/* * 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. */ #include "config.h" #include "threadname.hh" #include "dnsdist.hh" #include "dnsdist-ecs.hh" #include "dnsdist-lua.hh" #include "dnsdist-lua-ffi.hh" #include "dnsdist-protobuf.hh" #include "dnsdist-kvs.hh" #include "dnsdist-svc.hh" #include "dolog.hh" #include "dnstap.hh" #include "dnswriter.hh" #include "ednsoptions.hh" #include "fstrm_logger.hh" #include "remote_logger.hh" #include "svc-records.hh" #include #ifdef HAVE_LIBCRYPTO #include "ipcipher.hh" #endif /* HAVE_LIBCRYPTO */ class DropAction : public DNSAction { public: DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { return Action::Drop; } std::string toString() const override { return "drop"; } }; class AllowAction : public DNSAction { public: DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { return Action::Allow; } std::string toString() const override { return "allow"; } }; class NoneAction : public DNSAction { public: // this action does not stop the processing DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { return Action::None; } std::string toString() const override { return "no op"; } }; class QPSAction : public DNSAction { public: QPSAction(int limit) : d_qps(QPSLimiter(limit, limit)) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (d_qps.lock()->check()) { return Action::None; } else { return Action::Drop; } } std::string toString() const override { return "qps limit to "+std::to_string(d_qps.lock()->getRate()); } private: mutable LockGuarded d_qps; }; class DelayAction : public DNSAction { public: DelayAction(int msec) : d_msec(msec) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { *ruleresult = std::to_string(d_msec); return Action::Delay; } std::string toString() const override { return "delay by "+std::to_string(d_msec)+ " msec"; } private: int d_msec; }; class TeeAction : public DNSAction { public: // this action does not stop the processing TeeAction(const ComboAddress& ca, bool addECS=false); ~TeeAction() override; DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override; std::string toString() const override; std::map getStats() const override; private: ComboAddress d_remote; std::thread d_worker; void worker(); int d_fd{-1}; mutable std::atomic d_senderrors{0}; unsigned long d_recverrors{0}; mutable std::atomic d_queries{0}; stat_t d_responses{0}; stat_t d_nxdomains{0}; stat_t d_servfails{0}; stat_t d_refuseds{0}; stat_t d_formerrs{0}; stat_t d_notimps{0}; stat_t d_noerrors{0}; mutable stat_t d_tcpdrops{0}; stat_t d_otherrcode{0}; std::atomic d_pleaseQuit{false}; bool d_addECS{false}; }; TeeAction::TeeAction(const ComboAddress& ca, bool addECS) : d_remote(ca), d_addECS(addECS) { d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0); try { SConnect(d_fd, d_remote); setNonBlocking(d_fd); d_worker=std::thread([this](){worker();}); } catch (...) { if (d_fd != -1) { close(d_fd); } throw; } } TeeAction::~TeeAction() { d_pleaseQuit=true; close(d_fd); d_worker.join(); } DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult) const { if (dq->overTCP()) { d_tcpdrops++; } else { ssize_t res; d_queries++; if(d_addECS) { PacketBuffer query(dq->getData()); bool ednsAdded = false; bool ecsAdded = false; std::string newECSOption; generateECSOption(dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, newECSOption, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength); if (!handleEDNSClientSubnet(query, dq->getMaximumSize(), dq->qname->wirelength(), ednsAdded, ecsAdded, dq->ecsOverride, newECSOption)) { return DNSAction::Action::None; } res = send(d_fd, query.data(), query.size(), 0); } else { res = send(d_fd, dq->getData().data(), dq->getData().size(), 0); } if (res <= 0) { d_senderrors++; } } return DNSAction::Action::None; } std::string TeeAction::toString() const { return "tee to "+d_remote.toStringWithPort(); } std::map TeeAction::getStats() const { return {{"queries", d_queries}, {"responses", d_responses}, {"recv-errors", d_recverrors}, {"send-errors", d_senderrors}, {"noerrors", d_noerrors}, {"nxdomains", d_nxdomains}, {"refuseds", d_refuseds}, {"servfails", d_servfails}, {"other-rcode", d_otherrcode}, {"tcp-drops", d_tcpdrops} }; } void TeeAction::worker() { setThreadName("dnsdist/TeeWork"); char packet[1500]; int res=0; struct dnsheader* dh=(struct dnsheader*)packet; for(;;) { res=waitForData(d_fd, 0, 250000); if(d_pleaseQuit) break; if(res < 0) { usleep(250000); continue; } if(res==0) continue; res=recv(d_fd, packet, sizeof(packet), 0); if(res <= (int)sizeof(struct dnsheader)) d_recverrors++; else d_responses++; if(dh->rcode == RCode::NoError) d_noerrors++; else if(dh->rcode == RCode::ServFail) d_servfails++; else if(dh->rcode == RCode::NXDomain) d_nxdomains++; else if(dh->rcode == RCode::Refused) d_refuseds++; else if(dh->rcode == RCode::FormErr) d_formerrs++; else if(dh->rcode == RCode::NotImp) d_notimps++; } } class PoolAction : public DNSAction { public: PoolAction(const std::string& pool) : d_pool(pool) {} DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { *ruleresult=d_pool; return Action::Pool; } std::string toString() const override { return "to pool "+d_pool; } private: std::string d_pool; }; class QPSPoolAction : public DNSAction { public: QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(QPSLimiter(limit, limit)), d_pool(pool) {} DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (d_qps.lock()->check()) { *ruleresult = d_pool; return Action::Pool; } else { return Action::None; } } std::string toString() const override { return "max " +std::to_string(d_qps.lock()->getRate())+" to pool "+d_pool; } private: mutable LockGuarded d_qps; const std::string d_pool; }; class RCodeAction : public DNSAction { public: RCodeAction(uint8_t rcode) : d_rcode(rcode) {} DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->getHeader()->rcode = d_rcode; dq->getHeader()->qr = true; // for good measure setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig); return Action::HeaderModify; } std::string toString() const override { return "set rcode "+std::to_string(d_rcode); } ResponseConfig d_responseConfig; private: uint8_t d_rcode; }; class ERCodeAction : public DNSAction { public: ERCodeAction(uint8_t rcode) : d_rcode(rcode) {} DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->getHeader()->rcode = (d_rcode & 0xF); dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4); dq->getHeader()->qr = true; // for good measure setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig); return Action::HeaderModify; } std::string toString() const override { return "set ercode "+ERCode::to_s(d_rcode); } ResponseConfig d_responseConfig; private: uint8_t d_rcode; }; class SpoofSVCAction : public DNSAction { public: SpoofSVCAction(const std::vector>& parameters) { d_payloads.reserve(parameters.size()); for (const auto& param : parameters) { std::vector payload; if (!generateSVCPayload(payload, param.second)) { throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters"); } d_totalPayloadsSize += payload.size(); d_payloads.push_back(std::move(payload)); for (const auto& hint : param.second.ipv4hints) { d_additionals4.insert({ param.second.target, ComboAddress(hint) }); } for (const auto& hint : param.second.ipv6hints) { d_additionals6.insert({ param.second.target, ComboAddress(hint) }); } } } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { /* it will likely be a bit bigger than that because of additionals */ uint16_t numberOfRecords = d_payloads.size(); const auto qnameWireLength = dq->qname->wirelength(); if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize)) { return Action::None; } PacketBuffer newPacket; newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize); GenericDNSPacketWriter pw(newPacket, *dq->qname, dq->qtype); for (const auto& payload : d_payloads) { pw.startRecord(*dq->qname, dq->qtype, d_responseConfig.ttl); pw.xfrBlob(payload); pw.commit(); } if (newPacket.size() < dq->getMaximumSize()) { for (const auto& additional : d_additionals4) { pw.startRecord(additional.first.isRoot() ? *dq->qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL); pw.xfrCAWithoutPort(4, additional.second); pw.commit(); } } if (newPacket.size() < dq->getMaximumSize()) { for (const auto& additional : d_additionals6) { pw.startRecord(additional.first.isRoot() ? *dq->qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL); pw.xfrCAWithoutPort(6, additional.second); pw.commit(); } } if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) { bool dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO; pw.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0); pw.commit(); } if (newPacket.size() >= dq->getMaximumSize()) { /* sorry! */ return Action::None; } pw.getHeader()->id = dq->getHeader()->id; pw.getHeader()->qr = true; // for good measure setResponseHeadersFromConfig(*pw.getHeader(), d_responseConfig); dq->getMutableData() = std::move(newPacket); return Action::HeaderModify; } std::string toString() const override { return "spoof SVC record "; } ResponseConfig d_responseConfig; private: std::vector> d_payloads; std::set> d_additionals4; std::set> d_additionals6; size_t d_totalPayloadsSize{0}; }; class TCAction : public DNSAction { public: DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { return Action::Truncate; } std::string toString() const override { return "tc=1 answer"; } }; class LuaAction : public DNSAction { public: typedef std::function >(DNSQuestion* dq)> func_t; LuaAction(const LuaAction::func_t& func) : d_func(func) {} DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { auto lock = g_lua.lock(); try { auto ret = d_func(dq); if (ruleresult) { if (boost::optional rule = std::get<1>(ret)) { *ruleresult = *rule; } else { // default to empty string ruleresult->clear(); } } return static_cast(std::get<0>(ret)); } catch (const std::exception &e) { warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSAction::Action::ServFail; } string toString() const override { return "Lua script"; } private: func_t d_func; }; class LuaResponseAction : public DNSResponseAction { public: typedef std::function >(DNSResponse* dr)> func_t; LuaResponseAction(const LuaResponseAction::func_t& func) : d_func(func) {} DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { auto lock = g_lua.lock(); try { auto ret = d_func(dr); if (ruleresult) { if (boost::optional rule = std::get<1>(ret)) { *ruleresult = *rule; } else { // default to empty string ruleresult->clear(); } } return static_cast(std::get<0>(ret)); } catch (const std::exception &e) { warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSResponseAction::Action::ServFail; } string toString() const override { return "Lua response script"; } private: func_t d_func; }; class LuaFFIAction: public DNSAction { public: typedef std::function func_t; LuaFFIAction(const LuaFFIAction::func_t& func): d_func(func) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dnsdist_ffi_dnsquestion_t dqffi(dq); try { auto lock = g_lua.lock(); auto ret = d_func(&dqffi); if (ruleresult) { if (dqffi.result) { *ruleresult = *dqffi.result; } else { // default to empty string ruleresult->clear(); } } return static_cast(ret); } catch (const std::exception &e) { warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSAction::Action::ServFail; } string toString() const override { return "Lua FFI script"; } private: func_t d_func; }; class LuaFFIPerThreadAction: public DNSAction { public: typedef std::function func_t; LuaFFIPerThreadAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { try { auto& state = t_perThreadStates[d_functionID]; if (!state.d_initialized) { setupLuaFFIPerThreadContext(state.d_luaContext); /* mark the state as initialized first so if there is a syntax error we only try to execute the code once */ state.d_initialized = true; state.d_func = state.d_luaContext.executeCode(d_functionCode); } if (!state.d_func) { /* the function was not properly initialized */ return DNSAction::Action::None; } dnsdist_ffi_dnsquestion_t dqffi(dq); auto ret = state.d_func(&dqffi); if (ruleresult) { if (dqffi.result) { *ruleresult = *dqffi.result; } else { // default to empty string ruleresult->clear(); } } return static_cast(ret); } catch (const std::exception &e) { warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSAction::Action::ServFail; } string toString() const override { return "Lua FFI per-thread script"; } private: struct PerThreadState { LuaContext d_luaContext; func_t d_func; bool d_initialized{false}; }; static std::atomic s_functionsCounter; static thread_local std::map t_perThreadStates; const std::string d_functionCode; const uint64_t d_functionID; }; std::atomic LuaFFIPerThreadAction::s_functionsCounter = 0; thread_local std::map LuaFFIPerThreadAction::t_perThreadStates; class LuaFFIResponseAction: public DNSResponseAction { public: typedef std::function func_t; LuaFFIResponseAction(const LuaFFIResponseAction::func_t& func): d_func(func) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { DNSQuestion* dq = dynamic_cast(dr); if (dq == nullptr) { return DNSResponseAction::Action::ServFail; } dnsdist_ffi_dnsquestion_t dqffi(dq); try { auto lock = g_lua.lock(); auto ret = d_func(&dqffi); if (ruleresult) { if (dqffi.result) { *ruleresult = *dqffi.result; } else { // default to empty string ruleresult->clear(); } } return static_cast(ret); } catch (const std::exception &e) { warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSResponseAction::Action::ServFail; } string toString() const override { return "Lua FFI script"; } private: func_t d_func; }; class LuaFFIPerThreadResponseAction: public DNSResponseAction { public: typedef std::function func_t; LuaFFIPerThreadResponseAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { DNSQuestion* dq = dynamic_cast(dr); if (dq == nullptr) { return DNSResponseAction::Action::ServFail; } try { auto& state = t_perThreadStates[d_functionID]; if (!state.d_initialized) { setupLuaFFIPerThreadContext(state.d_luaContext); /* mark the state as initialized first so if there is a syntax error we only try to execute the code once */ state.d_initialized = true; state.d_func = state.d_luaContext.executeCode(d_functionCode); } if (!state.d_func) { /* the function was not properly initialized */ return DNSResponseAction::Action::None; } dnsdist_ffi_dnsquestion_t dqffi(dq); auto ret = state.d_func(&dqffi); if (ruleresult) { if (dqffi.result) { *ruleresult = *dqffi.result; } else { // default to empty string ruleresult->clear(); } } return static_cast(ret); } catch (const std::exception &e) { warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what()); } catch (...) { warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]"); } return DNSResponseAction::Action::ServFail; } string toString() const override { return "Lua FFI per-thread script"; } private: struct PerThreadState { LuaContext d_luaContext; func_t d_func; bool d_initialized{false}; }; static std::atomic s_functionsCounter; static thread_local std::map t_perThreadStates; const std::string d_functionCode; const uint64_t d_functionID; }; std::atomic LuaFFIPerThreadResponseAction::s_functionsCounter = 0; thread_local std::map LuaFFIPerThreadResponseAction::t_perThreadStates; thread_local std::default_random_engine SpoofAction::t_randomEngine; DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const { uint16_t qtype = dq->qtype; // do we even have a response? if (d_cname.empty() && d_rawResponses.empty() && d_types.count(qtype) == 0) { return Action::None; } vector addrs; vector rawResponses; unsigned int totrdatalen = 0; uint16_t numberOfRecords = 0; if (!d_cname.empty()) { qtype = QType::CNAME; totrdatalen += d_cname.getStorage().size(); numberOfRecords = 1; } else if (!d_rawResponses.empty()) { rawResponses.reserve(d_rawResponses.size()); for(const auto& rawResponse : d_rawResponses){ totrdatalen += rawResponse.size(); rawResponses.push_back(rawResponse); ++numberOfRecords; } if (rawResponses.size() > 1) { shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine); } } else { for(const auto& addr : d_addrs) { if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) { continue; } totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); addrs.push_back(addr); ++numberOfRecords; } } if (addrs.size() > 1) { shuffle(addrs.begin(), addrs.end(), t_randomEngine); } unsigned int qnameWireLength=0; DNSName ignore(reinterpret_cast(dq->getData().data()), dq->getData().size(), sizeof(dnsheader), false, 0, 0, &qnameWireLength); if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) { return Action::None; } bool dnssecOK = false; bool hadEDNS = false; if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) { hadEDNS = true; dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO; } auto& data = dq->getMutableData(); data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen); // there goes your EDNS uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4)); dq->getHeader()->qr = true; // for good measure setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig); dq->getHeader()->ancount = 0; dq->getHeader()->arcount = 0; // for now, forget about your EDNS, we're marching over it uint32_t ttl = htonl(d_responseConfig.ttl); unsigned char recordstart[] = {0xc0, 0x0c, // compressed name 0, 0, // QTYPE 0, QClass::IN, 0, 0, 0, 0, // TTL 0, 0 }; // rdata length static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); memcpy(&recordstart[6], &ttl, sizeof(ttl)); bool raw = false; if (qtype == QType::CNAME) { const auto& wireData = d_cname.getStorage(); // Note! This doesn't do compression! uint16_t rdataLen = htons(wireData.length()); qtype = htons(qtype); memcpy(&recordstart[2], &qtype, sizeof(qtype)); memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); memcpy(dest, recordstart, sizeof(recordstart)); dest += sizeof(recordstart); memcpy(dest, wireData.c_str(), wireData.length()); dq->getHeader()->ancount++; } else if (!rawResponses.empty()) { qtype = htons(qtype); for(const auto& rawResponse : rawResponses){ uint16_t rdataLen = htons(rawResponse.size()); memcpy(&recordstart[2], &qtype, sizeof(qtype)); memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); memcpy(dest, recordstart, sizeof(recordstart)); dest += sizeof(recordstart); memcpy(dest, rawResponse.c_str(), rawResponse.size()); dest += rawResponse.size(); dq->getHeader()->ancount++; } raw = true; } else { for(const auto& addr : addrs) { uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA); memcpy(&recordstart[2], &qtype, sizeof(qtype)); memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); memcpy(dest, recordstart, sizeof(recordstart)); dest += sizeof(recordstart); memcpy(dest, addr.sin4.sin_family == AF_INET ? reinterpret_cast(&addr.sin4.sin_addr.s_addr) : reinterpret_cast(&addr.sin6.sin6_addr.s6_addr), addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); dq->getHeader()->ancount++; } } dq->getHeader()->ancount = htons(dq->getHeader()->ancount); if (hadEDNS && raw == false) { addEDNS(dq->getMutableData(), dq->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0); } return Action::HeaderModify; } class SetMacAddrAction : public DNSAction { public: // this action does not stop the processing SetMacAddrAction(uint16_t code) : d_code(code) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { std::string mac = getMACAddress(*dq->remote); if (mac.empty()) { return Action::None; } std::string optRData; generateEDNSOption(d_code, mac, optRData); if (dq->getHeader()->arcount) { bool ednsAdded = false; bool optionAdded = false; PacketBuffer newContent; newContent.reserve(dq->getData().size()); if (!slowRewriteEDNSOptionInQueryWithRecords(dq->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) { return Action::None; } if (newContent.size() > dq->getMaximumSize()) { return Action::None; } dq->getMutableData() = std::move(newContent); if (!dq->ednsAdded && ednsAdded) { dq->ednsAdded = true; } return Action::None; } auto& data = dq->getMutableData(); if (generateOptRR(optRData, data, dq->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) { dq->getHeader()->arcount = htons(1); // make sure that any EDNS sent by the backend is removed before forwarding the response to the client dq->ednsAdded = true; } return Action::None; } std::string toString() const override { return "add EDNS MAC (code=" + std::to_string(d_code) + ")"; } private: uint16_t d_code{3}; }; class SetEDNSOptionAction : public DNSAction { public: // this action does not stop the processing SetEDNSOptionAction(uint16_t code, const std::string& data) : d_code(code), d_data(data) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { std::string optRData; generateEDNSOption(d_code, d_data, optRData); if (dq->getHeader()->arcount) { bool ednsAdded = false; bool optionAdded = false; PacketBuffer newContent; newContent.reserve(dq->getData().size()); if (!slowRewriteEDNSOptionInQueryWithRecords(dq->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) { return Action::None; } if (newContent.size() > dq->getMaximumSize()) { return Action::None; } dq->getMutableData() = std::move(newContent); if (!dq->ednsAdded && ednsAdded) { dq->ednsAdded = true; } return Action::None; } auto& data = dq->getMutableData(); if (generateOptRR(optRData, data, dq->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) { dq->getHeader()->arcount = htons(1); // make sure that any EDNS sent by the backend is removed before forwarding the response to the client dq->ednsAdded = true; } return Action::None; } std::string toString() const override { return "add EDNS Option (code=" + std::to_string(d_code) + ")"; } private: uint16_t d_code; std::string d_data; }; class SetNoRecurseAction : public DNSAction { public: // this action does not stop the processing DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->getHeader()->rd = false; return Action::None; } std::string toString() const override { return "set rd=0"; } }; class LogAction : public DNSAction, public boost::noncopyable { public: // this action does not stop the processing LogAction() { } LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered) { if (str.empty()) { return; } if (!reopenLogFile()) { throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror()); } } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire); if (!fp) { if (!d_verboseOnly || g_verbose) { if (d_includeTimestamp) { infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast(dq->queryTime->tv_sec), static_cast(dq->queryTime->tv_nsec), dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).toString(), dq->getHeader()->id); } else { infolog("Packet from %s for %s %s with id %d", dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).toString(), dq->getHeader()->id); } } } else { if (d_binary) { const auto& out = dq->qname->getStorage(); if (d_includeTimestamp) { uint64_t tv_sec = static_cast(dq->queryTime->tv_sec); uint32_t tv_nsec = static_cast(dq->queryTime->tv_nsec); fwrite(&tv_sec, sizeof(tv_sec), 1, fp.get()); fwrite(&tv_nsec, sizeof(tv_nsec), 1, fp.get()); } uint16_t id = dq->getHeader()->id; fwrite(&id, sizeof(id), 1, fp.get()); fwrite(out.c_str(), 1, out.size(), fp.get()); fwrite(&dq->qtype, sizeof(dq->qtype), 1, fp.get()); fwrite(&dq->remote->sin4.sin_family, sizeof(dq->remote->sin4.sin_family), 1, fp.get()); if (dq->remote->sin4.sin_family == AF_INET) { fwrite(&dq->remote->sin4.sin_addr.s_addr, sizeof(dq->remote->sin4.sin_addr.s_addr), 1, fp.get()); } else if (dq->remote->sin4.sin_family == AF_INET6) { fwrite(&dq->remote->sin6.sin6_addr.s6_addr, sizeof(dq->remote->sin6.sin6_addr.s6_addr), 1, fp.get()); } fwrite(&dq->remote->sin4.sin_port, sizeof(dq->remote->sin4.sin_port), 1, fp.get()); } else { if (d_includeTimestamp) { fprintf(fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %d\n", static_cast(dq->queryTime->tv_sec), static_cast(dq->queryTime->tv_nsec), dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id); } else { fprintf(fp.get(), "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).toString().c_str(), dq->getHeader()->id); } } } return Action::None; } std::string toString() const override { if (!d_fname.empty()) { return "log to " + d_fname; } return "log"; } void reload() override { if (!reopenLogFile()) { warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror()); } } private: bool reopenLogFile() { // we are using a naked pointer here because we don't want fclose to be called // with a nullptr, which would happen if we constructor a shared_ptr with fclose // as a custom deleter and nullptr as a FILE* auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w"); if (!nfp) { /* don't fall on our sword when reopening */ return false; } auto fp = std::shared_ptr(nfp, fclose); nfp = nullptr; if (!d_buffered) { setbuf(fp.get(), 0); } std::atomic_store_explicit(&d_fp, fp, std::memory_order_release); return true; } std::string d_fname; std::shared_ptr d_fp{nullptr}; bool d_binary{true}; bool d_verboseOnly{true}; bool d_includeTimestamp{false}; bool d_append{false}; bool d_buffered{true}; }; class LogResponseAction : public DNSResponseAction, public boost::noncopyable { public: LogResponseAction() { } LogResponseAction(const std::string& str, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered) { if (str.empty()) { return; } if (!reopenLogFile()) { throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror()); } } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire); if (!fp) { if (!d_verboseOnly || g_verbose) { if (d_includeTimestamp) { infolog("[%u.%u] Answer to %s for %s %s (%s) with id %d", static_cast(dr->queryTime->tv_sec), static_cast(dr->queryTime->tv_nsec), dr->remote->toStringWithPort(), dr->qname->toString(), QType(dr->qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id); } else { infolog("Answer to %s for %s %s (%s) with id %d", dr->remote->toStringWithPort(), dr->qname->toString(), QType(dr->qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id); } } } else { if (d_includeTimestamp) { fprintf(fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %d\n", static_cast(dr->queryTime->tv_sec), static_cast(dr->queryTime->tv_nsec), dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id); } else { fprintf(fp.get(), "Answer to %s for %s %s (%s) with id %d\n", dr->remote->toStringWithPort().c_str(), dr->qname->toString().c_str(), QType(dr->qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id); } } return Action::None; } std::string toString() const override { if (!d_fname.empty()) { return "log to " + d_fname; } return "log"; } void reload() override { if (!reopenLogFile()) { warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror()); } } private: bool reopenLogFile() { // we are using a naked pointer here because we don't want fclose to be called // with a nullptr, which would happen if we constructor a shared_ptr with fclose // as a custom deleter and nullptr as a FILE* auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w"); if (!nfp) { /* don't fall on our sword when reopening */ return false; } auto fp = std::shared_ptr(nfp, fclose); nfp = nullptr; if (!d_buffered) { setbuf(fp.get(), 0); } std::atomic_store_explicit(&d_fp, fp, std::memory_order_release); return true; } std::string d_fname; std::shared_ptr d_fp{nullptr}; bool d_verboseOnly{true}; bool d_includeTimestamp{false}; bool d_append{false}; bool d_buffered{true}; }; class SetDisableValidationAction : public DNSAction { public: // this action does not stop the processing DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->getHeader()->cd = true; return Action::None; } std::string toString() const override { return "set cd=1"; } }; class SetSkipCacheAction : public DNSAction { public: // this action does not stop the processing DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->skipCache = true; return Action::None; } std::string toString() const override { return "skip cache"; } }; class SetSkipCacheResponseAction : public DNSResponseAction { public: DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { dr->skipCache = true; return Action::None; } std::string toString() const override { return "skip cache"; } }; class SetTempFailureCacheTTLAction : public DNSAction { public: // this action does not stop the processing SetTempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->tempFailureTTL = d_ttl; return Action::None; } std::string toString() const override { return "set tempfailure cache ttl to "+std::to_string(d_ttl); } private: uint32_t d_ttl; }; class SetECSPrefixLengthAction : public DNSAction { public: // this action does not stop the processing SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->ecsPrefixLength = dq->remote->sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength; return Action::None; } std::string toString() const override { return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength); } private: uint16_t d_v4PrefixLength; uint16_t d_v6PrefixLength; }; class SetECSOverrideAction : public DNSAction { public: // this action does not stop the processing SetECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->ecsOverride = d_ecsOverride; return Action::None; } std::string toString() const override { return "set ECS override to " + std::to_string(d_ecsOverride); } private: bool d_ecsOverride; }; class SetDisableECSAction : public DNSAction { public: // this action does not stop the processing DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->useECS = false; return Action::None; } std::string toString() const override { return "disable ECS"; } }; class SetECSAction : public DNSAction { public: // this action does not stop the processing SetECSAction(const Netmask& v4): d_v4(v4), d_hasV6(false) { } SetECSAction(const Netmask& v4, const Netmask& v6): d_v4(v4), d_v6(v6), d_hasV6(true) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->ecsSet = true; if (d_hasV6) { dq->ecs = dq->remote->isIPv4() ? d_v4 : d_v6; } else { dq->ecs = d_v4; } return Action::None; } std::string toString() const override { std::string result = "set ECS to " + d_v4.toString(); if (d_hasV6) { result += " / " + d_v6.toString(); } return result; } private: Netmask d_v4; Netmask d_v6; bool d_hasV6; }; static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol) { if (protocol == dnsdist::Protocol::DoUDP) { return DnstapMessage::ProtocolType::DoUDP; } else if (protocol == dnsdist::Protocol::DoTCP) { return DnstapMessage::ProtocolType::DoTCP; } else if (protocol == dnsdist::Protocol::DoT) { return DnstapMessage::ProtocolType::DoT; } else if (protocol == dnsdist::Protocol::DoH) { return DnstapMessage::ProtocolType::DoH; } else if (protocol == dnsdist::Protocol::DNSCryptUDP) { return DnstapMessage::ProtocolType::DNSCryptUDP; } else if (protocol == dnsdist::Protocol::DNSCryptTCP) { return DnstapMessage::ProtocolType::DNSCryptTCP; } throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString()); } class DnstapLogAction : public DNSAction, public boost::noncopyable { public: // this action does not stop the processing DnstapLogAction(const std::string& identity, std::shared_ptr& logger, boost::optional > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { static thread_local std::string data; data.clear(); DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dq->getProtocol()); DnstapMessage message(data, !dq->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, dq->remote, dq->local, protocol, reinterpret_cast(dq->getData().data()), dq->getData().size(), dq->queryTime, nullptr); { if (d_alterFunc) { auto lock = g_lua.lock(); (*d_alterFunc)(dq, &message); } } d_logger->queueData(data); return Action::None; } std::string toString() const override { return "remote log as dnstap to " + (d_logger ? d_logger->toString() : ""); } private: std::string d_identity; std::shared_ptr d_logger; boost::optional > d_alterFunc; }; class RemoteLogAction : public DNSAction, public boost::noncopyable { public: // this action does not stop the processing RemoteLogAction(std::shared_ptr& logger, boost::optional > alterFunc, const std::string& serverID, const std::string& ipEncryptKey): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (!dq->uniqueId) { dq->uniqueId = getUniqueID(); } DNSDistProtoBufMessage message(*dq); if (!d_serverID.empty()) { message.setServerIdentity(d_serverID); } #if HAVE_LIBCRYPTO if (!d_ipEncryptKey.empty()) { message.setRequestor(encryptCA(*dq->remote, d_ipEncryptKey)); } #endif /* HAVE_LIBCRYPTO */ if (d_alterFunc) { auto lock = g_lua.lock(); (*d_alterFunc)(dq, &message); } static thread_local std::string data; data.clear(); message.serialize(data); d_logger->queueData(data); return Action::None; } std::string toString() const override { return "remote log to " + (d_logger ? d_logger->toString() : ""); } private: std::shared_ptr d_logger; boost::optional > d_alterFunc; std::string d_serverID; std::string d_ipEncryptKey; }; class SNMPTrapAction : public DNSAction { public: // this action does not stop the processing SNMPTrapAction(const std::string& reason): d_reason(reason) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (g_snmpAgent && g_snmpTrapsEnabled) { g_snmpAgent->sendDNSTrap(*dq, d_reason); } return Action::None; } std::string toString() const override { return "send SNMP trap"; } private: std::string d_reason; }; class SetTagAction : public DNSAction { public: // this action does not stop the processing SetTagAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { dq->setTag(d_tag, d_value); return Action::None; } std::string toString() const override { return "set tag '" + d_tag + "' to value '" + d_value + "'"; } private: std::string d_tag; std::string d_value; }; class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable { public: // this action does not stop the processing DnstapLogResponseAction(const std::string& identity, std::shared_ptr& logger, boost::optional > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { static thread_local std::string data; struct timespec now; gettime(&now, true); data.clear(); DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dr->getProtocol()); DnstapMessage message(data, DnstapMessage::MessageType::client_response, d_identity, dr->remote, dr->local, protocol, reinterpret_cast(dr->getData().data()), dr->getData().size(), dr->queryTime, &now); { if (d_alterFunc) { auto lock = g_lua.lock(); (*d_alterFunc)(dr, &message); } } d_logger->queueData(data); return Action::None; } std::string toString() const override { return "log response as dnstap to " + (d_logger ? d_logger->toString() : ""); } private: std::string d_identity; std::shared_ptr d_logger; boost::optional > d_alterFunc; }; class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable { public: // this action does not stop the processing RemoteLogResponseAction(std::shared_ptr& logger, boost::optional > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { if (!dr->uniqueId) { dr->uniqueId = getUniqueID(); } DNSDistProtoBufMessage message(*dr, d_includeCNAME); if (!d_serverID.empty()) { message.setServerIdentity(d_serverID); } #if HAVE_LIBCRYPTO if (!d_ipEncryptKey.empty()) { message.setRequestor(encryptCA(*dr->remote, d_ipEncryptKey)); } #endif /* HAVE_LIBCRYPTO */ if (d_alterFunc) { auto lock = g_lua.lock(); (*d_alterFunc)(dr, &message); } static thread_local std::string data; data.clear(); message.serialize(data); d_logger->queueData(data); return Action::None; } std::string toString() const override { return "remote log response to " + (d_logger ? d_logger->toString() : ""); } private: std::shared_ptr d_logger; boost::optional > d_alterFunc; std::string d_serverID; std::string d_ipEncryptKey; bool d_includeCNAME; }; class DropResponseAction : public DNSResponseAction { public: DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { return Action::Drop; } std::string toString() const override { return "drop"; } }; class AllowResponseAction : public DNSResponseAction { public: DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { return Action::Allow; } std::string toString() const override { return "allow"; } }; class DelayResponseAction : public DNSResponseAction { public: DelayResponseAction(int msec) : d_msec(msec) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { *ruleresult = std::to_string(d_msec); return Action::Delay; } std::string toString() const override { return "delay by "+std::to_string(d_msec)+ " msec"; } private: int d_msec; }; class SNMPTrapResponseAction : public DNSResponseAction { public: // this action does not stop the processing SNMPTrapResponseAction(const std::string& reason): d_reason(reason) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { if (g_snmpAgent && g_snmpTrapsEnabled) { g_snmpAgent->sendDNSTrap(*dr, d_reason); } return Action::None; } std::string toString() const override { return "send SNMP trap"; } private: std::string d_reason; }; class SetTagResponseAction : public DNSResponseAction { public: // this action does not stop the processing SetTagResponseAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value) { } DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override { dr->setTag(d_tag, d_value); return Action::None; } std::string toString() const override { return "set tag '" + d_tag + "' to value '" + d_value + "'"; } private: std::string d_tag; std::string d_value; }; class ContinueAction : public DNSAction { public: // this action does not stop the processing ContinueAction(std::shared_ptr& action): d_action(action) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (d_action) { /* call the action */ auto action = (*d_action)(dq, ruleresult); bool drop = false; /* apply the changes if needed (pool selection, flags, etc */ processRulesResult(action, *dq, *ruleresult, drop); } /* but ignore the resulting action no matter what */ return Action::None; } std::string toString() const override { if (d_action) { return "continue after: " + (d_action ? d_action->toString() : ""); } else { return "no op"; } } private: std::shared_ptr d_action; }; #ifdef HAVE_DNS_OVER_HTTPS class HTTPStatusAction: public DNSAction { public: HTTPStatusAction(int code, const PacketBuffer& body, const std::string& contentType): d_body(body), d_contentType(contentType), d_code(code) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (!dq->du) { return Action::None; } dq->du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType); dq->getHeader()->qr = true; // for good measure setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig); return Action::HeaderModify; } std::string toString() const override { return "return an HTTP status of " + std::to_string(d_code); } ResponseConfig d_responseConfig; private: PacketBuffer d_body; std::string d_contentType; int d_code; }; #endif /* HAVE_DNS_OVER_HTTPS */ class KeyValueStoreLookupAction : public DNSAction { public: // this action does not stop the processing KeyValueStoreLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { std::vector keys = d_key->getKeys(*dq); std::string result; for (const auto& key : keys) { if (d_kvs->getValue(key, result) == true) { break; } } dq->setTag(d_tag, std::move(result)); return Action::None; } std::string toString() const override { return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'"; } private: std::shared_ptr d_kvs; std::shared_ptr d_key; std::string d_tag; }; class KeyValueStoreRangeLookupAction : public DNSAction { public: // this action does not stop the processing KeyValueStoreRangeLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { std::vector keys = d_key->getKeys(*dq); std::string result; for (const auto& key : keys) { if (d_kvs->getRangeValue(key, result) == true) { break; } } dq->setTag(d_tag, std::move(result)); return Action::None; } std::string toString() const override { return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'"; } private: std::shared_ptr d_kvs; std::shared_ptr d_key; std::string d_tag; }; class NegativeAndSOAAction: public DNSAction { public: NegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum): d_zone(zone), d_mname(mname), d_rname(rname), d_ttl(ttl), d_serial(serial), d_refresh(refresh), d_retry(retry), d_expire(expire), d_minimum(minimum), d_nxd(nxd) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (!setNegativeAndAdditionalSOA(*dq, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_serial, d_refresh, d_retry, d_expire, d_minimum)) { return Action::None; } setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig); return Action::Allow; } std::string toString() const override { return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA"; } ResponseConfig d_responseConfig; private: DNSName d_zone; DNSName d_mname; DNSName d_rname; uint32_t d_ttl; uint32_t d_serial; uint32_t d_refresh; uint32_t d_retry; uint32_t d_expire; uint32_t d_minimum; bool d_nxd; }; class SetProxyProtocolValuesAction : public DNSAction { public: // this action does not stop the processing SetProxyProtocolValuesAction(const std::vector>& values) { d_values.reserve(values.size()); for (const auto& value : values) { d_values.push_back({value.second, value.first}); } } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (!dq->proxyProtocolValues) { dq->proxyProtocolValues = make_unique>(); } *(dq->proxyProtocolValues) = d_values; return Action::None; } std::string toString() const override { return "set Proxy-Protocol values"; } private: std::vector d_values; }; class SetAdditionalProxyProtocolValueAction : public DNSAction { public: // this action does not stop the processing SetAdditionalProxyProtocolValueAction(uint8_t type, const std::string& value): d_value(value), d_type(type) { } DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override { if (!dq->proxyProtocolValues) { dq->proxyProtocolValues = make_unique>(); } dq->proxyProtocolValues->push_back({ d_value, d_type }); return Action::None; } std::string toString() const override { return "add a Proxy-Protocol value of type " + std::to_string(d_type); } private: std::string d_value; uint8_t d_type; }; template static void addAction(GlobalStateHolder > *someRuleActions, const luadnsrule_t& var, const std::shared_ptr& action, boost::optional& params) { setLuaSideEffect(); std::string name; boost::uuids::uuid uuid; uint64_t creationOrder; parseRuleParams(params, uuid, name, creationOrder); auto rule = makeRule(var); someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector& ruleactions){ ruleactions.push_back({std::move(rule), std::move(action), std::move(name), std::move(uuid), creationOrder}); }); } typedef std::unordered_map > responseParams_t; static void parseResponseConfig(boost::optional vars, ResponseConfig& config) { if (vars) { if (vars->count("ttl")) { config.ttl = boost::get((*vars)["ttl"]); } if (vars->count("aa")) { config.setAA = boost::get((*vars)["aa"]); } if (vars->count("ad")) { config.setAD = boost::get((*vars)["ad"]); } if (vars->count("ra")) { config.setRA = boost::get((*vars)["ra"]); } } } void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config) { if (config.setAA) { dh.aa = *config.setAA; } if (config.setAD) { dh.ad = *config.setAD; } else { dh.ad = false; } if (config.setRA) { dh.ra = *config.setRA; } else { dh.ra = dh.rd; // for good measure } } void setupLuaActions(LuaContext& luaCtx) { luaCtx.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr action, boost::optional params) { boost::uuids::uuid uuid; uint64_t creationOrder; std::string name; parseRuleParams(params, uuid, name, creationOrder); auto rule = makeRule(dnsrule); DNSDistRuleAction ra({std::move(rule), action, std::move(name), uuid, creationOrder}); return std::make_shared(ra); }); luaCtx.writeFunction("addAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era, boost::optional params) { if (era.type() != typeid(std::shared_ptr)) { throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?"); } addAction(&g_ruleactions, var, boost::get >(era), params); }); luaCtx.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era, boost::optional params) { if (era.type() != typeid(std::shared_ptr)) { throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); } addAction(&g_respruleactions, var, boost::get >(era), params); }); luaCtx.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, boost::variant, std::shared_ptr> era, boost::optional params) { if (era.type() != typeid(std::shared_ptr)) { throw std::runtime_error("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); } addAction(&g_cachehitrespruleactions, var, boost::get >(era), params); }); luaCtx.writeFunction("addSelfAnsweredResponseAction", [](luadnsrule_t var, boost::variant, std::shared_ptr> era, boost::optional params) { if (era.type() != typeid(std::shared_ptr)) { throw std::runtime_error("addSelfAnsweredResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); } addAction(&g_selfansweredrespruleactions, var, boost::get >(era), params); }); luaCtx.registerFunction("printStats", [](const DNSAction& ta) { setLuaNoSideEffect(); auto stats = ta.getStats(); for(const auto& s : stats) { g_outputBuffer+=s.first+"\t"; if((uint64_t)s.second == s.second) g_outputBuffer += std::to_string((uint64_t)s.second)+"\n"; else g_outputBuffer += std::to_string(s.second)+"\n"; } }); luaCtx.writeFunction("getAction", [](unsigned int num) { setLuaNoSideEffect(); boost::optional> ret; auto ruleactions = g_ruleactions.getCopy(); if(num < ruleactions.size()) ret=ruleactions[num].d_action; return ret; }); luaCtx.registerFunction("getStats", &DNSAction::getStats); luaCtx.registerFunction("reload", &DNSAction::reload); luaCtx.registerFunction("reload", &DNSResponseAction::reload); luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) { setLuaSideEffect(); return std::shared_ptr(new LuaAction(func)); }); luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) { setLuaSideEffect(); return std::shared_ptr(new LuaFFIAction(func)); }); luaCtx.writeFunction("LuaFFIPerThreadAction", [](std::string code) { setLuaSideEffect(); return std::shared_ptr(new LuaFFIPerThreadAction(code)); }); luaCtx.writeFunction("SetNoRecurseAction", []() { return std::shared_ptr(new SetNoRecurseAction); }); luaCtx.writeFunction("NoRecurseAction", []() { warnlog("access to NoRecurseAction is deprecated and will be removed in a future version, please use SetNoRecurseAction instead"); return std::shared_ptr(new SetNoRecurseAction); }); luaCtx.writeFunction("SetMacAddrAction", [](int code) { return std::shared_ptr(new SetMacAddrAction(code)); }); luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) { return std::shared_ptr(new SetEDNSOptionAction(code, data)); }); luaCtx.writeFunction("MacAddrAction", [](int code) { warnlog("access to MacAddrAction is deprecated and will be removed in a future version, please use SetMacAddrAction instead"); return std::shared_ptr(new SetMacAddrAction(code)); }); luaCtx.writeFunction("PoolAction", [](const std::string& a) { return std::shared_ptr(new PoolAction(a)); }); luaCtx.writeFunction("QPSAction", [](int limit) { return std::shared_ptr(new QPSAction(limit)); }); luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& a) { return std::shared_ptr(new QPSPoolAction(limit, a)); }); luaCtx.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional vars) { vector addrs; if(auto s = boost::get(&inp)) { addrs.push_back(ComboAddress(*s)); } else { const auto& v = boost::get>>(inp); for(const auto& a: v) { addrs.push_back(ComboAddress(a.second)); } } auto ret = std::shared_ptr(new SpoofAction(addrs)); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); return ret; }); luaCtx.writeFunction("SpoofSVCAction", [](const std::vector>& parameters, boost::optional vars) { auto ret = std::shared_ptr(new SpoofSVCAction(parameters)); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); return ret; }); luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional vars) { auto ret = std::shared_ptr(new SpoofAction(DNSName(a))); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); return ret; }); luaCtx.writeFunction("SpoofRawAction", [](boost::variant>> inp, boost::optional vars) { vector raws; if(auto s = boost::get(&inp)) { raws.push_back(*s); } else { const auto& v = boost::get>>(inp); for(const auto& raw: v) { raws.push_back(raw.second); } } auto ret = std::shared_ptr(new SpoofAction(raws)); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); return ret; }); luaCtx.writeFunction("DropAction", []() { return std::shared_ptr(new DropAction); }); luaCtx.writeFunction("AllowAction", []() { return std::shared_ptr(new AllowAction); }); luaCtx.writeFunction("NoneAction", []() { return std::shared_ptr(new NoneAction); }); luaCtx.writeFunction("DelayAction", [](int msec) { return std::shared_ptr(new DelayAction(msec)); }); luaCtx.writeFunction("TCAction", []() { return std::shared_ptr(new TCAction); }); luaCtx.writeFunction("SetDisableValidationAction", []() { return std::shared_ptr(new SetDisableValidationAction); }); luaCtx.writeFunction("DisableValidationAction", []() { warnlog("access to DisableValidationAction is deprecated and will be removed in a future version, please use SetDisableValidationAction instead"); return std::shared_ptr(new SetDisableValidationAction); }); luaCtx.writeFunction("LogAction", [](boost::optional fname, boost::optional binary, boost::optional append, boost::optional buffered, boost::optional verboseOnly, boost::optional includeTimestamp) { return std::shared_ptr(new LogAction(fname ? *fname : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false)); }); luaCtx.writeFunction("LogResponseAction", [](boost::optional fname, boost::optional append, boost::optional buffered, boost::optional verboseOnly, boost::optional includeTimestamp) { return std::shared_ptr(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false)); }); luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional vars) { auto ret = std::shared_ptr(new RCodeAction(rcode)); auto rca = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, rca->d_responseConfig); return ret; }); luaCtx.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional vars) { auto ret = std::shared_ptr(new ERCodeAction(rcode)); auto erca = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, erca->d_responseConfig); return ret; }); luaCtx.writeFunction("SetSkipCacheAction", []() { return std::shared_ptr(new SetSkipCacheAction); }); luaCtx.writeFunction("SkipCacheAction", []() { warnlog("access to SkipCacheAction is deprecated and will be removed in a future version, please use SetSkipCacheAction instead"); return std::shared_ptr(new SetSkipCacheAction); }); luaCtx.writeFunction("SetSkipCacheResponseAction", []() { return std::shared_ptr(new SetSkipCacheResponseAction); }); luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) { return std::shared_ptr(new SetTempFailureCacheTTLAction(maxTTL)); }); luaCtx.writeFunction("TempFailureCacheTTLAction", [](int maxTTL) { warnlog("access to TempFailureCacheTTLAction is deprecated and will be removed in a future version, please use SetTempFailureCacheTTLAction instead"); return std::shared_ptr(new SetTempFailureCacheTTLAction(maxTTL)); }); luaCtx.writeFunction("DropResponseAction", []() { return std::shared_ptr(new DropResponseAction); }); luaCtx.writeFunction("AllowResponseAction", []() { return std::shared_ptr(new AllowResponseAction); }); luaCtx.writeFunction("DelayResponseAction", [](int msec) { return std::shared_ptr(new DelayResponseAction(msec)); }); luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) { setLuaSideEffect(); return std::shared_ptr(new LuaResponseAction(func)); }); luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) { setLuaSideEffect(); return std::shared_ptr(new LuaFFIResponseAction(func)); }); luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](std::string code) { setLuaSideEffect(); return std::shared_ptr(new LuaFFIPerThreadResponseAction(code)); }); luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional> vars) { if (logger) { // avoids potentially-evaluated-expression warning with clang. RemoteLoggerInterface& rl = *logger.get(); if (typeid(rl) != typeid(RemoteLogger)) { // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong. throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction.")); } } std::string serverID; std::string ipEncryptKey; if (vars) { if (vars->count("serverID")) { serverID = boost::get((*vars)["serverID"]); } if (vars->count("ipEncryptKey")) { ipEncryptKey = boost::get((*vars)["ipEncryptKey"]); } } return std::shared_ptr(new RemoteLogAction(logger, alterFunc, serverID, ipEncryptKey)); }); luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional includeCNAME, boost::optional> vars) { if (logger) { // avoids potentially-evaluated-expression warning with clang. RemoteLoggerInterface& rl = *logger.get(); if (typeid(rl) != typeid(RemoteLogger)) { // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong. throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction."); } } std::string serverID; std::string ipEncryptKey; if (vars) { if (vars->count("serverID")) { serverID = boost::get((*vars)["serverID"]); } if (vars->count("ipEncryptKey")) { ipEncryptKey = boost::get((*vars)["ipEncryptKey"]); } } return std::shared_ptr(new RemoteLogResponseAction(logger, alterFunc, serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false)); }); luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr logger, boost::optional > alterFunc) { return std::shared_ptr(new DnstapLogAction(identity, logger, alterFunc)); }); luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr logger, boost::optional > alterFunc) { return std::shared_ptr(new DnstapLogResponseAction(identity, logger, alterFunc)); }); luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional addECS) { return std::shared_ptr(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false)); }); luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) { return std::shared_ptr(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength)); }); luaCtx.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) { warnlog("access to ECSPrefixLengthAction is deprecated and will be removed in a future version, please use SetECSPrefixLengthAction instead"); return std::shared_ptr(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength)); }); luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) { return std::shared_ptr(new SetECSOverrideAction(ecsOverride)); }); luaCtx.writeFunction("ECSOverrideAction", [](bool ecsOverride) { warnlog("access to ECSOverrideAction is deprecated and will be removed in a future version, please use SetECSOverrideAction instead"); return std::shared_ptr(new SetECSOverrideAction(ecsOverride)); }); luaCtx.writeFunction("SetDisableECSAction", []() { return std::shared_ptr(new SetDisableECSAction()); }); luaCtx.writeFunction("DisableECSAction", []() { warnlog("access to DisableECSAction is deprecated and will be removed in a future version, please use SetDisableECSAction instead"); return std::shared_ptr(new SetDisableECSAction()); }); luaCtx.writeFunction("SetECSAction", [](const std::string v4, boost::optional v6) { if (v6) { return std::shared_ptr(new SetECSAction(Netmask(v4), Netmask(*v6))); } return std::shared_ptr(new SetECSAction(Netmask(v4))); }); luaCtx.writeFunction("SNMPTrapAction", [](boost::optional reason) { #ifdef HAVE_NET_SNMP return std::shared_ptr(new SNMPTrapAction(reason ? *reason : "")); #else throw std::runtime_error("NET SNMP support is required to use SNMPTrapAction()"); #endif /* HAVE_NET_SNMP */ }); luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional reason) { #ifdef HAVE_NET_SNMP return std::shared_ptr(new SNMPTrapResponseAction(reason ? *reason : "")); #else throw std::runtime_error("NET SNMP support is required to use SNMPTrapResponseAction()"); #endif /* HAVE_NET_SNMP */ }); luaCtx.writeFunction("SetTagAction", [](std::string tag, std::string value) { return std::shared_ptr(new SetTagAction(tag, value)); }); luaCtx.writeFunction("TagAction", [](std::string tag, std::string value) { warnlog("access to TagAction is deprecated and will be removed in a future version, please use SetTagAction instead"); return std::shared_ptr(new SetTagAction(tag, value)); }); luaCtx.writeFunction("SetTagResponseAction", [](std::string tag, std::string value) { return std::shared_ptr(new SetTagResponseAction(tag, value)); }); luaCtx.writeFunction("TagResponseAction", [](std::string tag, std::string value) { warnlog("access to TagResponseAction is deprecated and will be removed in a future version, please use SetTagResponseAction instead"); return std::shared_ptr(new SetTagResponseAction(tag, value)); }); luaCtx.writeFunction("ContinueAction", [](std::shared_ptr action) { return std::shared_ptr(new ContinueAction(action)); }); #ifdef HAVE_DNS_OVER_HTTPS luaCtx.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional contentType, boost::optional vars) { auto ret = std::shared_ptr(new HTTPStatusAction(status, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : "")); auto hsa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, hsa->d_responseConfig); return ret; }); #endif /* HAVE_DNS_OVER_HTTPS */ luaCtx.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag) { return std::shared_ptr(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)); }); luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag) { return std::shared_ptr(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)); }); luaCtx.writeFunction("NegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional vars) { auto ret = std::shared_ptr(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum)); auto action = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, action->d_responseConfig); return ret; }); luaCtx.writeFunction("SetNegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional vars) { warnlog("access to SetNegativeAndSOAAction is deprecated and will be removed in a future version, please use NegativeAndSOAAction instead"); auto ret = std::shared_ptr(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum)); auto action = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, action->d_responseConfig); return ret; }); luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector>& values) { return std::shared_ptr(new SetProxyProtocolValuesAction(values)); }); luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) { return std::shared_ptr(new SetAdditionalProxyProtocolValueAction(type, value)); }); }