Skip to content

Commit

Permalink
Merge pull request #14664 from rgacogne/ddist-ffi-proxy-protocol-inco…
Browse files Browse the repository at this point in the history
…ming

dnsdist: Add a FFI accessor to incoming proxy protocol values
  • Loading branch information
rgacogne authored Sep 30, 2024
2 parents 362ef6d + 7053085 commit 3f0cb1a
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 1 deletion.
2 changes: 2 additions & 0 deletions pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ typedef struct dnsdist_ffi_proxy_protocol_value {
size_t dnsdist_ffi_generate_proxy_protocol_payload(size_t addrSize, const void* srcAddr, const void* dstAddr, uint16_t srcPort, uint16_t dstPort, bool tcp, size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values, void* out, size_t outSize) __attribute__ ((visibility ("default")));
size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi_dnsquestion_t* dq, const size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values, void* out, const size_t outSize) __attribute__ ((visibility ("default")));
bool dnsdist_ffi_dnsquestion_add_proxy_protocol_values(dnsdist_ffi_dnsquestion_t* dnsQuestion, const size_t valuesCount, const dnsdist_ffi_proxy_protocol_value_t* values) __attribute__ ((visibility ("default")));
// returns the length of the resulting 'out' array. 'out' is not set if the length is 0. Note that the return value will get invalidated as soon as a new value is added via dnsdist_ffi_dnsquestion_add_proxy_protocol_values().
size_t dnsdist_ffi_dnsquestion_get_proxy_protocol_values(dnsdist_ffi_dnsquestion_t* dnsQuestion, const dnsdist_ffi_proxy_protocol_value_t** out) __attribute__((visibility("default")));

typedef struct dnsdist_ffi_domain_list_t dnsdist_ffi_domain_list_t;
typedef struct dnsdist_ffi_address_list_t dnsdist_ffi_address_list_t;
Expand Down
23 changes: 23 additions & 0 deletions pdns/dnsdistdist/dnsdist-lua-ffi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,29 @@ bool dnsdist_ffi_dnsquestion_add_proxy_protocol_values(dnsdist_ffi_dnsquestion_t
return true;
}

size_t dnsdist_ffi_dnsquestion_get_proxy_protocol_values(dnsdist_ffi_dnsquestion_t* dnsQuestion, const dnsdist_ffi_proxy_protocol_value_t** out)
{
if (dnsQuestion == nullptr || dnsQuestion->dq == nullptr || !dnsQuestion->dq->proxyProtocolValues) {
return 0;
}

if (out == nullptr) {
return dnsQuestion->proxyProtocolValuesVect->size();
}

dnsQuestion->proxyProtocolValuesVect = std::make_unique<std::vector<dnsdist_ffi_proxy_protocol_value_t>>(dnsQuestion->dq->proxyProtocolValues->size());
for (size_t counter = 0; counter < dnsQuestion->dq->proxyProtocolValues->size(); ++counter) {
const auto& entry = dnsQuestion->dq->proxyProtocolValues->at(counter);
auto& targetEntry = dnsQuestion->proxyProtocolValuesVect->at(counter);
targetEntry.size = entry.content.size();
targetEntry.value = entry.content.data();
targetEntry.type = entry.type;
}

*out = dnsQuestion->proxyProtocolValuesVect->data();
return dnsQuestion->proxyProtocolValuesVect->size();
}

struct dnsdist_ffi_domain_list_t
{
std::vector<std::string> d_domains;
Expand Down
1 change: 1 addition & 0 deletions pdns/dnsdistdist/dnsdist-lua-ffi.hh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct dnsdist_ffi_dnsquestion_t
std::unique_ptr<std::vector<dnsdist_ffi_ednsoption_t>> ednsOptionsVect;
std::unique_ptr<std::vector<dnsdist_ffi_http_header_t>> httpHeadersVect;
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;
};

Expand Down
53 changes: 53 additions & 0 deletions pdns/dnsdistdist/test-dnsdist-lua-ffi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,59 @@ BOOST_AUTO_TEST_CASE(test_ProxyProtocolQuery)
}
}

BOOST_AUTO_TEST_CASE(test_ProxyProtocolIncoming)
{
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);

{
/* invalid dq */
const dnsdist_ffi_proxy_protocol_value_t* out = nullptr;
BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_proxy_protocol_values(nullptr, &out), 0U);
}
{
/* invalid pointer */
BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_proxy_protocol_values(&lightDQ, nullptr), 0U);
}
{
/* no proxy protocol values */
const dnsdist_ffi_proxy_protocol_value_t* out = nullptr;
BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_proxy_protocol_values(&lightDQ, &out), 0U);
}

{
/* add some proxy protocol TLV values */
dnsQuestion.proxyProtocolValues = std::make_unique<std::vector<ProxyProtocolValue>>();
dnsQuestion.proxyProtocolValues->emplace_back(ProxyProtocolValue{"foo", 42});
dnsQuestion.proxyProtocolValues->emplace_back(ProxyProtocolValue{"bar", 255});
dnsQuestion.proxyProtocolValues->emplace_back(ProxyProtocolValue{"", 0});
const dnsdist_ffi_proxy_protocol_value_t* out = nullptr;
auto count = dnsdist_ffi_dnsquestion_get_proxy_protocol_values(&lightDQ, &out);
BOOST_REQUIRE_EQUAL(count, 3U);
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic): sorry, this is a C API
BOOST_CHECK_EQUAL(out[0].type, 42U);
BOOST_CHECK_EQUAL(out[0].value, "foo");
BOOST_CHECK_EQUAL(out[1].type, 255U);
BOOST_CHECK_EQUAL(out[1].value, "bar");
BOOST_CHECK_EQUAL(out[2].type, 0U);
BOOST_CHECK_EQUAL(out[2].value, "");
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic): sorry, this is a C API
}
}

BOOST_AUTO_TEST_CASE(test_PacketOverlay)
{
const DNSName target("powerdns.com.");
Expand Down
132 changes: 131 additions & 1 deletion regression-tests.dnsdist/test_ProxyProtocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ def testNoHeader(self):

def testIncomingProxyDest(self):
"""
Incoming Proxy Protocol: values from Lua
Incoming Proxy Protocol: get forwarded destination
"""
name = 'get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
Expand Down Expand Up @@ -862,6 +862,136 @@ def tearDownClass(cls):
backgroundThreads[backgroundThread] = False
cls.killProcess(cls._dnsdist)

class TestProxyProtocolIncomingValuesViaLua(DNSDistTest):
"""
Check that dnsdist can retrieve incoming Proxy Protocol TLV values via Lua
"""

_config_template = """
setProxyProtocolACL( { "127.0.0.1/32" } )
function checkValues(dq)
if dq.localaddr:toStringWithPort() ~= '[2001:db8::9]:9999' then
return DNSAction.Spoof, "invalid.local.addr."
end
if dq.remoteaddr:toStringWithPort() ~= '[2001:db8::8]:8888' then
return DNSAction.Spoof, "invalid.remote.addr."
end
local values = dq:getProxyProtocolValues()
if #values ~= 3 then
return DNSAction.Spoof, #values .. ".invalid.values.count."
end
if values[2] ~= 'foo' then
return DNSAction.Spoof, "2.foo.value.missing."
end
if values[3] ~= 'proxy' then
return DNSAction.Spoof, "3.proxy.value.missing."
end
return DNSAction.Spoof, "ok."
end
local ffi = require("ffi")
local C = ffi.C
ffi.cdef[[
typedef unsigned int socklen_t;
const char *inet_ntop(int af, const void *restrict src,
char *restrict dst, socklen_t size);
]]
local ret_ptr = ffi.new("const char *[1]")
local ret_ptr_param = ffi.cast("const void **", ret_ptr)
local ret_size = ffi.new("size_t[1]")
local ret_size_param = ffi.cast("size_t*", ret_size)
local ret_pp_ptr = ffi.new("const dnsdist_ffi_proxy_protocol_value_t*[1]")
local ret_pp_ptr_param = ffi.cast("const dnsdist_ffi_proxy_protocol_value_t**", ret_pp_ptr)
local inet_buffer = ffi.new("char[?]", 256)
function sendResult(dqffi, str)
C.dnsdist_ffi_dnsquestion_set_result(dqffi, str, #str)
return DNSAction.Spoof
end
function checkValuesFFI(dqffi)
C.dnsdist_ffi_dnsquestion_get_localaddr(dqffi, ret_ptr_param, ret_size_param)
local addr = C.inet_ntop(10, ret_ptr[0], inet_buffer, 256)
if addr == nil or ffi.string(addr) ~= '2001:db8::9' then
return sendResult(dqffi, "invalid.local.addr.")
end
C.dnsdist_ffi_dnsquestion_get_remoteaddr(dqffi, ret_ptr_param, ret_size_param)
local addr = C.inet_ntop(10, ret_ptr[0], inet_buffer, 256)
if addr == nil or ffi.string(addr) ~= '2001:db8::8' then
return sendResult(dqffi, "invalid.remote.addr.")
end
local count = tonumber(C.dnsdist_ffi_dnsquestion_get_proxy_protocol_values(dqffi, ret_pp_ptr_param))
if count ~= 2 then
return sendResult(dqffi, count .. ".invalid.values.count.")
end
local foo_seen = false
local proxy_seen = false
for counter = 0, count - 1 do
local entry = ret_pp_ptr[0][counter]
if entry.type == 2 and ffi.string(entry.value, entry.size) == 'foo' then
foo_seen = true
elseif entry.type == 3 and ffi.string(entry.value, entry.size) == 'proxy' then
proxy_seen = true
end
end
if not foo_seen then
return sendResult(dqffi, "2.foo.value.missing.")
end
if not proxy_seen then
return sendResult(dqffi, "3.proxy.value.missing.")
end
return sendResult(dqffi, "ok.")
end
addAction("proxy-protocol-incoming-values-via-lua.tests.powerdns.com.", LuaAction(checkValues))
addAction("proxy-protocol-incoming-values-via-lua-ffi.tests.powerdns.com.", LuaFFIAction(checkValuesFFI))
newServer{address="127.0.0.1:%d"}
"""

def testProxyUDPWithValuesFromLua(self):
"""
Incoming Proxy Protocol: values from Lua
"""
destAddr = "2001:db8::9"
destPort = 9999
srcAddr = "2001:db8::8"
srcPort = 8888
names = ['proxy-protocol-incoming-values-via-lua.tests.powerdns.com.',
'proxy-protocol-incoming-values-via-lua-ffi.tests.powerdns.com.'
]
for name in names:
query = dns.message.make_query(name, 'A', 'IN')
# dnsdist set RA = RD for spoofed responses
query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)

expectedResponse = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
60,
dns.rdataclass.IN,
dns.rdatatype.CNAME,
'ok.')
expectedResponse.answer.append(rrset)

udpPayload = ProxyProtocol.getPayload(False, False, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
(_, receivedResponse) = self.sendUDPQuery(udpPayload + query.to_wire(), response=None, useQueue=False, rawQuery=True)
self.assertEqual(expectedResponse, receivedResponse)

conn = self.openTCPConnection(2.0)
try:
conn.send(udpPayload)
conn.send(struct.pack("!H", len(query.to_wire())))
conn.send(query.to_wire())
receivedResponse = self.recvTCPResponseOverConnection(conn)
except socket.timeout:
print('timeout')
self.assertEqual(expectedResponse, receivedResponse)

class TestProxyProtocolNotExpected(DNSDistTest):
"""
dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
Expand Down

0 comments on commit 3f0cb1a

Please sign in to comment.