Skip to content

Commit

Permalink
Merge pull request #6117 from rgacogne/ddist-dns-over-tls
Browse files Browse the repository at this point in the history
dnsdist: Add initial DNS over TLS support
  • Loading branch information
rgacogne committed Jan 12, 2018
2 parents 746a8d7 + a227f47 commit d24089b
Show file tree
Hide file tree
Showing 19 changed files with 1,540 additions and 23 deletions.
1 change: 1 addition & 0 deletions build-scripts/travis.sh
Expand Up @@ -407,6 +407,7 @@ build_dnsdist(){
--enable-unit-tests \
--enable-libsodium \
--enable-dnscrypt \
--enable-dns-over-tls \
--prefix=$HOME/dnsdist \
--disable-silent-rules"
run "make -k -j3"
Expand Down
3 changes: 3 additions & 0 deletions pdns/dnsdist-console.cc
Expand Up @@ -292,6 +292,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
{ "addLuaResponseAction", true, "x, func", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" },
{ "addCacheHitResponseAction", true, "DNS rule, DNS response action", "add a cache hit response rule" },
{ "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" },
{ "addTLSLocal", true, "addr, certFile, keyFile[,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate and key. The last parameter is a table" },
{ "AllowAction", true, "", "let these packets go through" },
{ "AllowResponseAction", true, "", "let these packets go through" },
{ "AllRule", true, "", "matches all traffic" },
Expand Down Expand Up @@ -326,6 +327,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
{ "getResponseRing", true, "", "return the current content of the response ring" },
{ "getServer", true, "n", "returns server with index n" },
{ "getServers", true, "", "returns a table with all defined servers" },
{ "getTLSContext", true, "n", "returns the TLS context with index n" },
{ "inClientStartup", true, "", "returns true during console client parsing of configuration" },
{ "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
{ "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
Expand Down Expand Up @@ -410,6 +412,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
{ "showServerPolicy", true, "", "show name of currently operational server selection policy" },
{ "showServers", true, "", "output all servers" },
{ "showTCPStats", true, "", "show some statistics regarding TCP" },
{ "showTLSContext", true, "", "list all the available TLS contexts" },
{ "showVersion", true, "", "show the current version" },
{ "shutdown", true, "", "shut down `dnsdist`" },
{ "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
Expand Down
124 changes: 123 additions & 1 deletion pdns/dnsdist-lua.cc
Expand Up @@ -35,6 +35,7 @@
#include "dnswriter.hh"
#include "dolog.hh"
#include "lock.hh"
#include "protobuf.hh"
#include "sodcrypto.hh"

#include <boost/logic/tribool.hpp>
Expand Down Expand Up @@ -490,7 +491,18 @@ void setupLuaConfig(bool client)
g_lua.writeFunction("shutdown", []() {
#ifdef HAVE_SYSTEMD
sd_notify(0, "STOPPING=1");
#endif
#endif /* HAVE_SYSTEMD */
#if 0
// Useful for debugging leaks, but might lead to race under load
// since other threads are still runing.
for(auto& frontend : g_tlslocals) {
frontend->cleanup();
}
g_tlslocals.clear();
#ifdef HAVE_PROTOBUF
google::protobuf::ShutdownProtobufLibrary();
#endif /* HAVE_PROTOBUF */
#endif /* 0 */
_exit(0);
} );

Expand Down Expand Up @@ -1363,6 +1375,116 @@ void setupLuaConfig(bool client)
g_outputBuffer="recvmmsg support is not available!\n";
#endif
});

g_lua.writeFunction("addTLSLocal", [client](const std::string& addr, const std::string& certFile, const std::string& keyFile, boost::optional<localbind_t> vars) {
if (client)
return;
#ifdef HAVE_DNS_OVER_TLS
setLuaSideEffect();
if (g_configurationDone) {
g_outputBuffer="addTLSLocal cannot be used at runtime!\n";
return;
}
shared_ptr<TLSFrontend> frontend = std::make_shared<TLSFrontend>();
frontend->d_certFile = certFile;
frontend->d_keyFile = keyFile;

if (vars) {
bool doTCP = true;
parseLocalBindVars(vars, doTCP, frontend->d_reusePort, frontend->d_tcpFastOpenQueueSize, frontend->d_interface, frontend->d_cpus);

if (vars->count("provider")) {
frontend->d_provider = boost::get<const string>((*vars)["provider"]);
}

if (vars->count("ciphers")) {
frontend->d_ciphers = boost::get<const string>((*vars)["ciphers"]);
}

if (vars->count("ticketKeyFile")) {
frontend->d_ticketKeyFile = boost::get<const string>((*vars)["ticketKeyFile"]);
}

if (vars->count("ticketsKeysRotationDelay")) {
frontend->d_ticketsKeyRotationDelay = std::stoi(boost::get<const string>((*vars)["ticketsKeysRotationDelay"]));
}

if (vars->count("numberOfTicketsKeys")) {
frontend->d_numberOfTicketsKeys = std::stoi(boost::get<const string>((*vars)["numberOfTicketsKeys"]));
}
}

try {
frontend->d_addr = ComboAddress(addr, 853);
vinfolog("Loading TLS provider %s", frontend->d_provider);
g_tlslocals.push_back(frontend); /// only works pre-startup, so no sync necessary
}
catch(const std::exception& e) {
g_outputBuffer="Error: "+string(e.what())+"\n";
}
#else
g_outputBuffer="DNS over TLS support is not present!\n";
#endif
});

g_lua.writeFunction("showTLSContexts", [client]() {
#ifdef HAVE_DNS_OVER_TLS
setLuaNoSideEffect();
try {
ostringstream ret;
boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
// 1 2 3 4 5
ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation" ) << endl;
size_t counter = 0;
for (const auto& ctx : g_tlslocals) {
ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
counter++;
}
g_outputBuffer = ret.str();
}
catch(const std::exception& e) {
g_outputBuffer = e.what();
throw;
}
#else
g_outputBuffer="DNS over TLS support is not present!\n";
#endif
});

g_lua.writeFunction("getTLSContext", [client](size_t index) {
std::shared_ptr<TLSCtx> result = nullptr;
#ifdef HAVE_DNS_OVER_TLS
setLuaNoSideEffect();
try {
if (index < g_tlslocals.size()) {
result = g_tlslocals.at(index)->getContext();
}
else {
errlog("Error: trying to get TLS context with index %zu but we only have %zu\n", index, g_tlslocals.size());
g_outputBuffer="Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + "\n";
}
}
catch(const std::exception& e) {
g_outputBuffer="Error: "+string(e.what())+"\n";
errlog("Error: %s\n", string(e.what()));
}
#else
g_outputBuffer="DNS over TLS support is not present!\n";
#endif
return result;
});

g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx> ctx) {
if (ctx != nullptr) {
ctx->rotateTicketsKey(time(nullptr));
}
});

g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx> ctx, const std::string& file) {
if (ctx != nullptr) {
ctx->loadTicketsKeys(file);
}
});
}

vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
Expand Down
47 changes: 29 additions & 18 deletions pdns/dnsdist-tcp.cc
Expand Up @@ -26,15 +26,16 @@
#include "dolog.hh"
#include "lock.hh"
#include "gettime.hh"
#include "tcpiohandler.hh"
#include <thread>
#include <atomic>

using std::thread;
using std::atomic;

/* TCP: the grand design.
We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
/* TCP: the grand design.
We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
we will not go there.
In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
Expand Down Expand Up @@ -184,9 +185,18 @@ catch(...) {
return false;
}

static bool sendResponseToClient(int fd, const char* response, uint16_t responseLen)
static bool getNonBlockingMsgLenFromClient(TCPIOHandler& handler, uint16_t* len)
try
{
return sendSizeAndMsgWithTimeout(fd, responseLen, response, g_tcpSendTimeout, nullptr, nullptr, 0, 0, 0);
uint16_t raw;
size_t ret = handler.read(&raw, sizeof raw, g_tcpRecvTimeout);
if(ret != sizeof raw)
return false;
*len = ntohs(raw);
return true;
}
catch(...) {
return false;
}

static bool maxConnectionDurationReached(unsigned int maxConnectionDuration, time_t start, unsigned int& remainingTime)
Expand Down Expand Up @@ -224,7 +234,7 @@ void* tcpClientThread(int pipefd)
{
/* we get launched with a pipe on which we receive file descriptors from clients that we own
from that point on */

bool outstanding = false;
time_t lastTCPCleanup = time(nullptr);

Expand All @@ -249,7 +259,7 @@ void* tcpClientThread(int pipefd)

g_tcpclientthreads->decrementQueuedCount();
ci=*citmp;
delete citmp;
delete citmp;

uint16_t qlen, rlen;
vector<uint8_t> rewrittenResponse;
Expand All @@ -267,12 +277,14 @@ void* tcpClientThread(int pipefd)
}

try {
TCPIOHandler handler(ci.fd, g_tcpRecvTimeout, ci.cs->tlsFrontend ? ci.cs->tlsFrontend->getContext() : nullptr, connectionStartTime);

for(;;) {
unsigned int remainingTime = 0;
ds = nullptr;
outstanding = false;

if(!getNonBlockingMsgLen(ci.fd, &qlen, g_tcpRecvTimeout)) {
if(!getNonBlockingMsgLenFromClient(handler, &qlen)) {
break;
}

Expand Down Expand Up @@ -303,8 +315,7 @@ void* tcpClientThread(int pipefd)
queryBuffer.reserve(qlen + 512);

char* query = &queryBuffer[0];
readn2WithTimeout(ci.fd, query, qlen, g_tcpRecvTimeout, remainingTime);

handler.read(query, qlen, g_tcpRecvTimeout, remainingTime);
#ifdef HAVE_DNSCRYPT
std::shared_ptr<DnsCryptQuery> dnsCryptQuery = nullptr;

Expand All @@ -316,7 +327,7 @@ void* tcpClientThread(int pipefd)

if (!decrypted) {
if (response.size() > 0) {
sendResponseToClient(ci.fd, reinterpret_cast<char*>(response.data()), (uint16_t) response.size());
handler.writeSizeAndMsg(response.data(), response.size(), g_tcpSendTimeout);
}
break;
}
Expand Down Expand Up @@ -355,7 +366,7 @@ void* tcpClientThread(int pipefd)
goto drop;
}
#endif
sendResponseToClient(ci.fd, query, dq.len);
handler.writeSizeAndMsg(query, dq.len, g_tcpSendTimeout);
g_stats.selfAnswered++;
continue;
}
Expand Down Expand Up @@ -402,7 +413,7 @@ void* tcpClientThread(int pipefd)
goto drop;
}
#endif
sendResponseToClient(ci.fd, cachedResponse, cachedResponseSize);
handler.writeSizeAndMsg(cachedResponse, cachedResponseSize, g_tcpSendTimeout);
g_stats.cacheHits++;
continue;
}
Expand All @@ -422,7 +433,7 @@ void* tcpClientThread(int pipefd)
goto drop;
}
#endif
sendResponseToClient(ci.fd, query, dq.len);
handler.writeSizeAndMsg(query, dq.len, g_tcpSendTimeout);
continue;
}

Expand Down Expand Up @@ -561,7 +572,7 @@ void* tcpClientThread(int pipefd)
goto drop;
}
#endif
if (!sendResponseToClient(ci.fd, response, responseLen)) {
if (!handler.writeSizeAndMsg(response, responseLen, g_tcpSendTimeout)) {
break;
}

Expand Down Expand Up @@ -595,15 +606,16 @@ void* tcpClientThread(int pipefd)
rewrittenResponse.clear();
}
}
catch(...){}
catch(...) {}

drop:;

vinfolog("Closing TCP client connection with %s", ci.remote.toStringWithPort());
if (ci.fd >= 0) {
close(ci.fd);
}
ci.fd = -1;

if (ds && outstanding) {
outstanding = false;
--ds->outstanding;
Expand Down Expand Up @@ -727,7 +739,6 @@ void* tcpAcceptorThread(void* p)
return 0;
}


bool getMsgLen32(int fd, uint32_t* len)
try
{
Expand Down

0 comments on commit d24089b

Please sign in to comment.