From a94673eadabd745259742c2347570175292e437b Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 26 Aug 2016 09:53:24 +0200 Subject: [PATCH] dnsdist: Add an optional Lua callback for altering a Protobuf message For anonymization purposes, for example. --- pdns/README-dnsdist.md | 21 +++++++++++++++-- pdns/dnsdist-lua.cc | 6 +++++ pdns/dnsdist-lua2.cc | 31 +++++++++++++++++++++---- pdns/dnsdistconf.lua | 16 +++++++++++++ pdns/dnsrulactions.hh | 18 +++++++++++++-- pdns/iputils.hh | 9 ++++++++ pdns/protobuf.cc | 52 ++++++++++++++++++++++++++++++++---------- pdns/protobuf.hh | 4 ++++ 8 files changed, 136 insertions(+), 21 deletions(-) diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 007d91b2fe..1fd11a7941 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -1274,8 +1274,8 @@ instantiate a server with additional parameters * `QPSPoolAction(maxqps, poolname)`: set the packet into the specified pool only if it **does not** exceed the specified QPS limits, letting the subsequent rules apply otherwise * `QPSAction(rule, maxqps)`: drop these packets if the QPS limits are exceeded * `RCodeAction(rcode)`: reply immediatly by turning the query into a response with the specified rcode - * `RemoteLogAction(RemoteLogger)`: send the content of this query to a remote logger via Protocol Buffer - * `RemoteLogResponseAction(RemoteLogger)`: send the content of this response to a remote logger via Protocol Buffer + * `RemoteLogAction(RemoteLogger [, alterFunction])`: send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes + * `RemoteLogResponseAction(RemoteLogger [,alterFunction])`: send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` * `SkipCacheAction()`: don't lookup the cache for this query, don't store the answer * `SpoofAction(ip[, ip])` or `SpoofAction({ip, ip, ..}): forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in * `SpoofCNAMEAction(cname)`: forge a response with the specified CNAME value @@ -1355,10 +1355,15 @@ instantiate a server with additional parameters * ComboAddress related: * `newCA(address)`: return a new ComboAddress * `getPort()`: return the port number + * `isIPv4()`: return true if the address is an IPv4, false otherwise + * `isIPv6()`: return true if the address is an IPv6, false otherwise + * `isMappedIPv4()`: return true if the address is an IPv4 mapped into an IPv6, false otherwise + * `mapToIPv4()`: convert an IPv4 address mapped in a v6 one into an IPv4 * `tostring()`: return in human-friendly format * `toString()`: alias for `tostring()` * `tostringWithPort()`: return in human-friendly format, with port number * `toStringWithPort()`: alias for `tostringWithPort()` + * `truncate(bits)`: truncate the address to the specified number of bits * DNSName related: * `newDNSName(name)`: make a DNSName based on this .-terminated name * member `countLabels()`: return the number of labels @@ -1422,6 +1427,18 @@ instantiate a server with additional parameters * member `getStats()`: print the block tables * member `unblock(ComboAddress)`: unblock this address * member `unblockQName(DNSName [, qtype=255])`: remove this qname from the block list + * DNSDistProtoBufMessage related: + * member `setBytes(bytes)`: set the size of the query + * member `setEDNSSubnet(Netmask)`: set the EDNS Subnet + * member `setQueryTime(sec, usec)`: in a response message, set the time at which the query has been received + * member `setQuestion(DNSName, qtype, qclass)`: set the question + * member `setRequestor(ComboAddress)`: set the requestor + * member `setRequestorFromString(string)`: set the requestor + * member `setResponder(ComboAddress)`: set the responder + * member `setResponderFromString(string)`: set the responder + * member `setResponseCode(rcode)`: set the response code + * member `setTime(sec, usec)`: set the time at which the query or response has been received + * member `toDebugString()`: return an string containing the content of the message * DynBPFFilter related: * function `newDynBPFFilter(BPFFilter)`: return a new DynBPFFilter object using this BPF Filter * member `block(ComboAddress[, seconds]): add this address to the underlying BPF Filter for `seconds` seconds (default to 10 seconds) diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 2b82fb97d3..c2dbe8070d 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1096,6 +1096,12 @@ vector> setupLua(bool client, const std::string& confi g_lua.registerFunction("toString", &ComboAddress::toString); g_lua.registerFunction("toStringWithPort", &ComboAddress::toStringWithPort); g_lua.registerFunction("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); + g_lua.registerFunction("truncate", &ComboAddress::truncate); + g_lua.registerFunction("isIPv4", &ComboAddress::isIPv4); + g_lua.registerFunction("isIPv6", &ComboAddress::isIPv6); + g_lua.registerFunction("isMappedIPv4", &ComboAddress::isMappedIPv4); + g_lua.registerFunction("mapToIPv4", &ComboAddress::mapToIPv4); + g_lua.registerFunction("isPartOf", &DNSName::isPartOf); g_lua.registerFunction("countLabels", &DNSName::countLabels); g_lua.registerFunction("wirelength", &DNSName::wirelength); diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index d30f6edb17..1eb7272f95 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -163,7 +163,6 @@ void moreLua(bool client) typedef NetmaskTree nmts_t; g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); }); - g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); }); g_lua.registerFunction("addMask", [](NetmaskGroup&nmg, const std::string& mask) { @@ -679,20 +678,42 @@ void moreLua(bool client) return std::shared_ptr(new DelayResponseAction(msec)); }); - g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr logger) { + g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional > alterFunc) { #ifdef HAVE_PROTOBUF - return std::shared_ptr(new RemoteLogAction(logger)); + return std::shared_ptr(new RemoteLogAction(logger, alterFunc)); #else throw std::runtime_error("Protobuf support is required to use RemoteLogAction"); #endif }); - g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger) { + g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional > alterFunc) { #ifdef HAVE_PROTOBUF - return std::shared_ptr(new RemoteLogResponseAction(logger)); + return std::shared_ptr(new RemoteLogResponseAction(logger, alterFunc)); #else throw std::runtime_error("Protobuf support is required to use RemoteLogResponseAction"); #endif }); + + g_lua.registerFunction("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); }); + g_lua.registerFunction("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); }); + g_lua.registerFunction("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); }); + g_lua.registerFunction("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); }); + g_lua.registerFunction("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); }); + g_lua.registerFunction("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); }); + g_lua.registerFunction("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); }); + + g_lua.registerFunction("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { + message.setRequestor(addr); + }); + g_lua.registerFunction("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { + message.setRequestor(str); + }); + g_lua.registerFunction("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { + message.setResponder(addr); + }); + g_lua.registerFunction("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { + message.setResponder(str); + }); + g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional timeout, boost::optional maxQueuedEntries, boost::optional reconnectWaitTime) { return std::make_shared(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? *maxQueuedEntries : 100, reconnectWaitTime ? *reconnectWaitTime : 1); }); diff --git a/pdns/dnsdistconf.lua b/pdns/dnsdistconf.lua index 028f49cac8..78ff583102 100644 --- a/pdns/dnsdistconf.lua +++ b/pdns/dnsdistconf.lua @@ -222,3 +222,19 @@ end addLuaAction("luaspoof2.powerdns.com.", spoof2rule) --]] + +-- alter a protobuf response for anonymization purposes +--[[ +function alterProtobuf(dq, protobuf) + requestor = newCA(dq.remoteaddr:toString()) + if requestor:isIPv4() then + requestor:truncate(24) + else + requestor:truncate(56) + end + protobuf:setRequestor(requestor) +end + +rl = newRemoteLogger("127.0.0.1:4242") +addAction(AllRule(), RemoteLogAction(rl, alterProtobuf)) +--]] diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh index 95ab9eeec7..fb3a707fa2 100644 --- a/pdns/dnsrulactions.hh +++ b/pdns/dnsrulactions.hh @@ -983,13 +983,19 @@ public: class RemoteLogAction : public DNSAction, public boost::noncopyable { public: - RemoteLogAction(std::shared_ptr logger): d_logger(logger) + RemoteLogAction(std::shared_ptr logger, boost::optional > alterFunc): d_logger(logger), d_alterFunc(alterFunc) { } DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override { #ifdef HAVE_PROTOBUF DNSDistProtoBufMessage message(DNSDistProtoBufMessage::Query, *dq); + { + if (d_alterFunc) { + std::lock_guard lock(g_luamutex); + (*d_alterFunc)(*dq, &message); + } + } std::string data; message.serialize(data); d_logger->queueData(data); @@ -1002,18 +1008,25 @@ public: } private: std::shared_ptr d_logger; + boost::optional > d_alterFunc; }; class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable { public: - RemoteLogResponseAction(std::shared_ptr logger): d_logger(logger) + RemoteLogResponseAction(std::shared_ptr logger, boost::optional > alterFunc): d_logger(logger), d_alterFunc(alterFunc) { } DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override { #ifdef HAVE_PROTOBUF DNSDistProtoBufMessage message(*dr); + { + if (d_alterFunc) { + std::lock_guard lock(g_luamutex); + (*d_alterFunc)(*dr, &message); + } + } std::string data; message.serialize(data); d_logger->queueData(data); @@ -1026,6 +1039,7 @@ public: } private: std::shared_ptr d_logger; + boost::optional > d_alterFunc; }; class DropResponseAction : public DNSResponseAction diff --git a/pdns/iputils.hh b/pdns/iputils.hh index 1cc3798ba5..8a27f5fa5e 100644 --- a/pdns/iputils.hh +++ b/pdns/iputils.hh @@ -218,6 +218,15 @@ union ComboAddress { sin4.sin_port=htons(port); } + bool isIPv6() const + { + return sin4.sin_family == AF_INET6; + } + bool isIPv4() const + { + return sin4.sin_family == AF_INET; + } + bool isMappedIPv4() const { if(sin4.sin_family!=AF_INET6) diff --git a/pdns/protobuf.cc b/pdns/protobuf.cc index d04fa2540b..5e7ec5607e 100644 --- a/pdns/protobuf.cc +++ b/pdns/protobuf.cc @@ -140,6 +140,44 @@ void DNSProtoBufMessage::addRRsFromPacket(const char* packet, const size_t len) #endif /* HAVE_PROTOBUF */ } +void DNSProtoBufMessage::setRequestor(const std::string& requestor) +{ +#ifdef HAVE_PROTOBUF + d_message.set_from(requestor); +#endif /* HAVE_PROTOBUF */ +} + +void DNSProtoBufMessage::setRequestor(const ComboAddress& requestor) +{ +#ifdef HAVE_PROTOBUF + if (requestor.sin4.sin_family == AF_INET) { + d_message.set_from(&requestor.sin4.sin_addr.s_addr, sizeof(requestor.sin4.sin_addr.s_addr)); + } + else if (requestor.sin4.sin_family == AF_INET6) { + d_message.set_from(&requestor.sin6.sin6_addr.s6_addr, sizeof(requestor.sin6.sin6_addr.s6_addr)); + } +#endif /* HAVE_PROTOBUF */ +} + +void DNSProtoBufMessage::setResponder(const std::string& responder) +{ +#ifdef HAVE_PROTOBUF + d_message.set_from(responder); +#endif /* HAVE_PROTOBUF */ +} + +void DNSProtoBufMessage::setResponder(const ComboAddress& responder) +{ +#ifdef HAVE_PROTOBUF + if (responder.sin4.sin_family == AF_INET) { + d_message.set_from(&responder.sin4.sin_addr.s_addr, sizeof(responder.sin4.sin_addr.s_addr)); + } + else if (responder.sin4.sin_family == AF_INET6) { + d_message.set_from(&responder.sin6.sin6_addr.s6_addr, sizeof(responder.sin6.sin6_addr.s6_addr)); + } +#endif /* HAVE_PROTOBUF */ +} + void DNSProtoBufMessage::serialize(std::string& data) const { #ifdef HAVE_PROTOBUF @@ -178,20 +216,10 @@ void DNSProtoBufMessage::update(const boost::uuids::uuid& uuid, const ComboAddre d_message.set_socketprotocol(isTCP ? PBDNSMessage_SocketProtocol_TCP : PBDNSMessage_SocketProtocol_UDP); if (responder) { - if (responder->sin4.sin_family == AF_INET) { - d_message.set_to(&responder->sin4.sin_addr.s_addr, sizeof(responder->sin4.sin_addr.s_addr)); - } - else if (responder->sin4.sin_family == AF_INET6) { - d_message.set_to(&responder->sin6.sin6_addr.s6_addr, sizeof(responder->sin6.sin6_addr.s6_addr)); - } + setResponder(*responder); } if (requestor) { - if (requestor->sin4.sin_family == AF_INET) { - d_message.set_from(&requestor->sin4.sin_addr.s_addr, sizeof(requestor->sin4.sin_addr.s_addr)); - } - else if (requestor->sin4.sin_family == AF_INET6) { - d_message.set_from(&requestor->sin6.sin6_addr.s6_addr, sizeof(requestor->sin6.sin6_addr.s6_addr)); - } + setRequestor(*requestor); } } diff --git a/pdns/protobuf.hh b/pdns/protobuf.hh index c1cd49e4db..9170795e6a 100644 --- a/pdns/protobuf.hh +++ b/pdns/protobuf.hh @@ -41,6 +41,10 @@ public: void setResponseCode(uint8_t rcode); void addRRsFromPacket(const char* packet, const size_t len); void serialize(std::string& data) const; + void setRequestor(const std::string& requestor); + void setRequestor(const ComboAddress& requestor); + void setResponder(const std::string& responder); + void setResponder(const ComboAddress& responder); std::string toDebugString() const; #ifdef HAVE_PROTOBUF -- 2.47.2