static thread_local std::string data;
data.clear();
message.serialize(data);
+ if (!dnsquestion->d_rawProtobufContent.empty()) {
+ data.insert(data.end(), dnsquestion->d_rawProtobufContent.begin(), dnsquestion->d_rawProtobufContent.end());
+ }
remoteLoggerQueueData(*d_logger, data);
return Action::None;
static thread_local std::string data;
data.clear();
message.serialize(data);
+ if (!response->d_rawProtobufContent.empty()) {
+ data.insert(data.end(), response->d_rawProtobufContent.begin(), response->d_rawProtobufContent.end());
+ }
d_logger->queueData(data);
return Action::None;
#include "dnsdist-snmp.hh"
#include "dnsparser.hh"
+#include "protozero.hh"
+
+static void addMetaKeyAndValuesToProtobufContent(DNSQuestion& dnsQuestion, const std::string& key, const LuaArray<boost::variant<int64_t, std::string>>& values)
+{
+#if !defined(DISABLE_PROTOBUF)
+ protozero::pbf_writer pbfWriter{dnsQuestion.d_rawProtobufContent};
+ protozero::pbf_writer pbfMetaWriter{pbfWriter, static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::Field::meta)};
+ pbfMetaWriter.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaField::key), key);
+ protozero::pbf_writer pbfMetaValueWriter{pbfMetaWriter, static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaField::value)};
+ for (const auto& value : values) {
+ if (value.second.type() == typeid(std::string)) {
+ pbfMetaValueWriter.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaValueField::stringVal), boost::get<std::string>(value.second));
+ }
+ else {
+ pbfMetaValueWriter.add_uint64(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaValueField::intVal), boost::get<int64_t>(value.second));
+ }
+ }
+ pbfMetaValueWriter.commit();
+ pbfMetaWriter.commit();
+#endif /* DISABLE_PROTOBUF */
+}
+
// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
void setupLuaBindingsDNSQuestion([[maybe_unused]] LuaContext& luaCtx)
{
return *dnsQuestion.ids.qTag;
});
+ luaCtx.registerFunction<void (DNSQuestion::*)(std::string, LuaArray<boost::variant<int64_t, std::string>>)>("setMetaKey", [](DNSQuestion& dnsQuestion, const std::string& key, const LuaArray<boost::variant<int64_t, std::string>>& values) {
+ addMetaKeyAndValuesToProtobufContent(dnsQuestion, key, values);
+ });
+
luaCtx.registerFunction<void (DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dnsQuestion, const LuaArray<std::string>& values) {
if (!dnsQuestion.proxyProtocolValues) {
dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
luaCtx.registerFunction<uint8_t (DNSResponse::*)()>("getRestartCount", [](DNSResponse& dnsResponse) {
return dnsResponse.ids.restartCount;
});
+
+ luaCtx.registerFunction<void (DNSResponse::*)(std::string, LuaArray<boost::variant<int64_t, std::string>>)>("setMetaKey", [](DNSResponse& dnsResponse, const std::string& key, const LuaArray<boost::variant<int64_t, std::string>>& values) {
+ addMetaKeyAndValuesToProtobufContent(dnsResponse, key, values);
+ });
+
#endif /* DISABLE_NON_FFI_DQ_BINDINGS */
}
void dnsdist_ffi_svc_record_parameters_free(dnsdist_ffi_svc_record_parameters* parameters) __attribute__ ((visibility ("default")));
bool dnsdist_ffi_dnsquestion_generate_svc_response(dnsdist_ffi_dnsquestion_t* dnsQuestion, const dnsdist_ffi_svc_record_parameters** parametersList, size_t parametersListSize, uint32_t ttl) __attribute__ ((visibility ("default")));
+
+/* this function adds a new key to the raw meta buffer. It can only be called with the same key on a given query once, and dnsdist_ffi_dnsquestion_meta_end_key should always be called after values have been added */
+void dnsdist_ffi_dnsquestion_meta_begin_key(dnsdist_ffi_dnsquestion_t* dnsQuestion, const char* key, size_t keyLen) __attribute__ ((visibility ("default")));
+/* this function should never be called if dnsdist_ffi_dnsquestion_meta_begin_key has not been called first */
+void dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(dnsdist_ffi_dnsquestion_t* dnsQuestion, const char* value, size_t valueLen) __attribute__ ((visibility ("default")));
+/* this function should never be called if dnsdist_ffi_dnsquestion_meta_begin_key has not been called first */
+void dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(dnsdist_ffi_dnsquestion_t* dnsQuestion, int64_t value) __attribute__ ((visibility ("default")));
+/* this function should never be called if dnsdist_ffi_dnsquestion_meta_begin_key has not been called first */
+void dnsdist_ffi_dnsquestion_meta_end_key(dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default")));
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory): this is a C API, RAII is not an option
delete parameters;
}
+
+void dnsdist_ffi_dnsquestion_meta_begin_key([[maybe_unused]] dnsdist_ffi_dnsquestion_t* dnsQuestion, [[maybe_unused]] const char* key, [[maybe_unused]] size_t keyLen)
+{
+#ifndef DISABLE_PROTOBUF
+ if (dnsQuestion == nullptr || key == nullptr || keyLen == 0) {
+ return;
+ }
+
+ if (dnsQuestion->pbfWriter.valid()) {
+ vinfolog("Error in dnsdist_ffi_dnsquestion_meta_begin_key: the previous key has not been ended");
+ return;
+ }
+
+ dnsQuestion->pbfWriter = protozero::pbf_writer{dnsQuestion->dq->d_rawProtobufContent};
+ dnsQuestion->pbfMetaWriter = protozero::pbf_writer{dnsQuestion->pbfWriter, static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::Field::meta)};
+ dnsQuestion->pbfMetaWriter.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaField::key), protozero::data_view(key, keyLen));
+ dnsQuestion->pbfMetaValueWriter = protozero::pbf_writer {dnsQuestion->pbfMetaWriter, static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaField::value)};
+#endif /* DISABLE_PROTOBUF */
+}
+
+void dnsdist_ffi_dnsquestion_meta_add_str_value_to_key([[maybe_unused]] dnsdist_ffi_dnsquestion_t* dnsQuestion, [[maybe_unused]] const char* value, [[maybe_unused]] size_t valueLen)
+{
+#ifndef DISABLE_PROTOBUF
+ if (dnsQuestion == nullptr || value == nullptr || valueLen == 0) {
+ return;
+ }
+
+ if (!dnsQuestion->pbfMetaValueWriter.valid()) {
+ vinfolog("Error in dnsdist_ffi_dnsquestion_meta_add_str_value_to_key: trying to add a value without starting a key");
+ return;
+ }
+
+ dnsQuestion->pbfMetaValueWriter.add_string(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaValueField::stringVal), protozero::data_view(value, valueLen));
+#endif /* DISABLE_PROTOBUF */
+}
+
+void dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key([[maybe_unused]] dnsdist_ffi_dnsquestion_t* dnsQuestion, [[maybe_unused]] int64_t value)
+{
+#ifndef DISABLE_PROTOBUF
+ if (dnsQuestion == nullptr) {
+ return;
+ }
+
+ if (!dnsQuestion->pbfMetaValueWriter.valid()) {
+ vinfolog("Error in dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key: trying to add a value without starting a key");
+ return;
+ }
+
+ dnsQuestion->pbfMetaValueWriter.add_uint64(static_cast<protozero::pbf_tag_type>(pdns::ProtoZero::Message::MetaValueField::intVal), value);
+#endif /* DISABLE_PROTOBUF */
+}
+
+void dnsdist_ffi_dnsquestion_meta_end_key([[maybe_unused]] dnsdist_ffi_dnsquestion_t* dnsQuestion)
+{
+#ifndef DISABLE_PROTOBUF
+ if (dnsQuestion == nullptr) {
+ return;
+ }
+ if (!dnsQuestion->pbfWriter.valid()) {
+ vinfolog("Error in dnsdist_ffi_dnsquestion_meta_end_key: trying to end a key that has not been started");
+ return;
+ }
+
+ try {
+ /* reset the pbf writer so that the sizes are properly updated */
+ dnsQuestion->pbfMetaValueWriter.commit();
+ dnsQuestion->pbfMetaWriter.commit();
+ dnsQuestion->pbfWriter = protozero::pbf_writer();
+ }
+ catch (const std::exception& exp) {
+ vinfolog("Error in dnsdist_ffi_dnsquestion_meta_end_key: %s", exp.what());
+ }
+#endif /* DISABLE_PROTOBUF */
+}
#pragma once
#include "dnsdist.hh"
+#include "protozero.hh"
extern "C"
{
std::unique_ptr<std::vector<dnsdist_ffi_tag_t>> tagsVect;
std::unique_ptr<std::vector<dnsdist_ffi_proxy_protocol_value_t>> proxyProtocolValuesVect;
std::unique_ptr<std::unordered_map<std::string, std::string>> httpHeaders;
+#ifndef DISABLE_PROTOBUF
+ protozero::pbf_writer pbfWriter;
+ protozero::pbf_writer pbfMetaWriter;
+ protozero::pbf_writer pbfMetaValueWriter;
+#endif /* DISABLE_PROTOBUF */
};
// dnsdist_ffi_dnsresponse_t is a lightuserdata
InternalQueryState& ids;
std::unique_ptr<Netmask> ecs{nullptr};
std::string sni; /* Server Name Indication, if any (DoT or DoH) */
+#if !defined(DISABLE_PROTOBUF)
+ std::string d_rawProtobufContent; /* protobuf-encoded content to add to protobuf messages */
+#endif /* DISABLE_PROTOBUF */
mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */
std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr};
std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
:param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx)
:param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ``application/dns-message``.
+ .. method:: DNSQuestion:setMetaKey(key, values)
+
+ .. versionadded:: 2.0.0
+
+ Set a meta-data entry to be exported in the ``meta`` field of ProtoBuf messages.
+
+ :param string key: The key
+ :param list values: A list containing strings, integers, or a mix of integers and strings
+
.. method:: DNSQuestion:setNegativeAndAdditionalSOA(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum)
.. versionadded:: 1.5.0
dep_boost,
dep_boost_test,
dep_lua,
+ dep_protozero,
],
)
)
dep_boost_test,
dep_ffi_interface,
dep_lua,
+ dep_protozero,
],
}
}
#include <boost/test/unit_test.hpp>
#include "dnsdist-lua-ffi.hh"
+#include "base64.hh"
#include "dnsdist-cache.hh"
#include "dnsdist-configuration.hh"
#include "dnsdist-rings.hh"
dnsdist_ffi_svc_record_parameters_free(parameters);
}
+#if !defined(DISABLE_PROTOBUF)
+BOOST_AUTO_TEST_CASE(test_meta_values)
+{
+
+ InternalQueryState ids;
+ ids.origRemote = ComboAddress("192.0.2.1:4242");
+ ids.origDest = ComboAddress("192.0.2.255:53");
+ ids.qtype = QType::A;
+ ids.qclass = QClass::IN;
+ ids.protocol = dnsdist::Protocol::DoUDP;
+ ids.qname = DNSName("www.powerdns.com.");
+ ids.queryRealTime.start();
+ PacketBuffer query;
+ GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+ pwQ.getHeader()->rd = 1;
+ pwQ.getHeader()->id = htons(42);
+
+ DNSQuestion dnsQuestion(ids, query);
+ dnsdist_ffi_dnsquestion_t lightDQ(&dnsQuestion);
+
+ {
+ /* check invalid parameters */
+ dnsdist_ffi_dnsquestion_meta_begin_key(nullptr, nullptr, 0);
+ dnsdist_ffi_dnsquestion_meta_begin_key(&lightDQ, nullptr, 0);
+ dnsdist_ffi_dnsquestion_meta_begin_key(&lightDQ, "some-key", 0);
+ dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(nullptr, nullptr, 0);
+ dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(&lightDQ, nullptr, 0);
+ dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(&lightDQ, "some-str-value", 0);
+ dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(nullptr, 0);
+ dnsdist_ffi_dnsquestion_meta_end_key(nullptr);
+ }
+
+ {
+ /* trying to end a key that has not been started */
+ dnsdist_ffi_dnsquestion_meta_end_key(&lightDQ);
+ }
+
+ {
+ const std::string key{"some-key"};
+ const std::string value1{"first value"};
+ const std::string value2{"second value"};
+ BOOST_CHECK_EQUAL(dnsQuestion.d_rawProtobufContent.size(), 0U);
+ dnsdist_ffi_dnsquestion_meta_begin_key(&lightDQ, key.data(), key.size());
+ /* we should not be able to begin a new key without ending it first */
+ dnsdist_ffi_dnsquestion_meta_begin_key(&lightDQ, key.data(), key.size());
+ dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(&lightDQ, value1.data(), value1.size());
+ dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(&lightDQ, 42);
+ dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(&lightDQ, value2.data(), value2.size());
+ dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(&lightDQ, -42);
+ dnsdist_ffi_dnsquestion_meta_end_key(&lightDQ);
+ BOOST_CHECK_EQUAL(dnsQuestion.d_rawProtobufContent.size(), 55U);
+ BOOST_CHECK_EQUAL(Base64Encode(dnsQuestion.d_rawProtobufContent), "sgE0Cghzb21lLWtleRIoCgtmaXJzdCB2YWx1ZRAqCgxzZWNvbmQgdmFsdWUQ1v//////////AQ==");
+ }
+}
+#endif /* DISABLE_PROTOBUF */
+
BOOST_AUTO_TEST_SUITE_END();
newServer{address="127.0.0.1:%s"}
rl = newRemoteLogger('127.0.0.1:%d')
+ local ffi = require("ffi")
+ local C = ffi.C
+ function add_meta(dq)
+ local key = "my-meta-key-1"
+ local key2 = "my-meta-key-2"
+ C.dnsdist_ffi_dnsquestion_meta_begin_key(dq, key, #key)
+ C.dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(dq, "test", 4)
+ C.dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(dq, -42)
+ C.dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(dq, "test2", 5)
+ C.dnsdist_ffi_dnsquestion_meta_end_key(dq)
+
+ local key2 = "my-meta-key-2"
+ C.dnsdist_ffi_dnsquestion_meta_begin_key(dq, key2, #key2)
+ C.dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(dq, "foo", 3)
+ C.dnsdist_ffi_dnsquestion_meta_add_int64_value_to_key(dq, 42)
+ C.dnsdist_ffi_dnsquestion_meta_add_str_value_to_key(dq, "bar", 3)
+ C.dnsdist_ffi_dnsquestion_meta_end_key(dq)
+
+ return DNSAction.None
+ end
+
+ function addMetaToResponse(dr)
+ dr:setMetaKey('my-meta-key-1', {'test', -42, 'test2'})
+ dr:setMetaKey('my-meta-key-2', {'foo', 42, 'bar'})
+ return DNSResponseAction.None
+ end
+
+ addAction(AllRule(), LuaFFIAction(add_meta))
addAction(AllRule(), SetTagAction('my-tag-key', 'my-tag-value'))
addAction(AllRule(), SetTagAction('my-empty-key', ''))
addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1', exportTags='*'}, {b64='b64-content', ['my-tag-export-name']='tag:my-tag-key'}))
+ addResponseAction(AllRule(), LuaResponseAction(addMetaToResponse))
addResponseAction(AllRule(), SetTagResponseAction('my-tag-key2', 'my-tag-value2'))
addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportTags='my-empty-key,my-tag-key2'}, {['my-tag-export-name']='tags'}))
"""
self.assertIn('my-tag-key:my-tag-value', msg.response.tags)
self.assertIn('my-empty-key', msg.response.tags)
# meta tags
- self.assertEqual(len(msg.meta), 2)
+ self.assertEqual(len(msg.meta), 4)
tags = {}
for entry in msg.meta:
tags[entry.key] = entry.value.stringVal
self.assertIn('b64', tags)
self.assertIn('my-tag-export-name', tags)
+ self.assertEqual(msg.meta[2].key, 'my-meta-key-1')
+ self.assertEqual(len(msg.meta[2].value.stringVal), 2)
+ self.assertIn('test', msg.meta[2].value.stringVal)
+ self.assertIn('test2', msg.meta[2].value.stringVal)
+ self.assertIn(-42, msg.meta[2].value.intVal)
+
+ self.assertEqual(msg.meta[3].key, 'my-meta-key-2')
+ self.assertEqual(len(msg.meta[3].value.stringVal), 2)
+ self.assertIn('foo', msg.meta[3].value.stringVal)
+ self.assertIn('bar', msg.meta[3].value.stringVal)
+ self.assertIn(42, msg.meta[3].value.intVal)
+
b64EncodedQuery = base64.b64encode(query.to_wire()).decode('ascii')
self.assertEqual(tags['b64'], [b64EncodedQuery])
self.assertEqual(tags['my-tag-export-name'], ['my-tag-value'])
self.assertIn('my-tag-key2:my-tag-value2', msg.response.tags)
self.assertIn('my-empty-key', msg.response.tags)
# meta tags
- self.assertEqual(len(msg.meta), 1)
+ self.assertEqual(len(msg.meta), 3)
self.assertEqual(msg.meta[0].key, 'my-tag-export-name')
self.assertEqual(len(msg.meta[0].value.stringVal), 3)
self.assertIn('my-tag-key:my-tag-value', msg.meta[0].value.stringVal)
# no ':' when the value is empty
self.assertIn('my-empty-key', msg.meta[0].value.stringVal)
+ self.assertEqual(msg.meta[1].key, 'my-meta-key-1')
+ self.assertEqual(len(msg.meta[1].value.stringVal), 2)
+ self.assertIn('test', msg.meta[1].value.stringVal)
+ self.assertIn('test2', msg.meta[1].value.stringVal)
+ self.assertIn(-42, msg.meta[1].value.intVal)
+
+ self.assertEqual(msg.meta[2].key, 'my-meta-key-2')
+ self.assertEqual(len(msg.meta[2].value.stringVal), 2)
+ self.assertIn('foo', msg.meta[2].value.stringVal)
+ self.assertIn('bar', msg.meta[2].value.stringVal)
+ self.assertIn(42, msg.meta[2].value.intVal)
+
class TestProtobufExtendedDNSErrorTags(DNSDistProtobufTest):
_config_params = ['_testServerPort', '_protobufServerPort']
_config_template = """