From 6d77f7f8d9a25bf0bcd0877c37c266c9bc3a17ab Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Mon, 24 Feb 2020 16:28:15 +0100 Subject: [PATCH] dnsdist: Add setProxyProtocolValuesAction() --- pdns/dnsdist-console.cc | 1 + pdns/dnsdist-lua-actions.cc | 35 ++++++++++ pdns/dnsdistdist/docs/reference/dq.rst | 2 +- pdns/dnsdistdist/docs/rules-actions.rst | 8 +++ .../test_ProxyProtocol.py | 66 +++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 6fe525e2dd..b1ec405a10 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -576,6 +576,7 @@ const std::vector g_consoleKeywords{ { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" }, { "showVersion", true, "", "show the current version" }, { "shutdown", true, "", "shut down `dnsdist`" }, + { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" }, { "SkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" }, { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" }, { "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"}, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 82ae55ffe3..c3e1e11360 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -1418,6 +1418,37 @@ private: bool d_nxd; }; +class SetProxyProtocolValuesAction : public DNSAction +{ +public: + 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; +}; + template static void addAction(GlobalStateHolder > *someRulActions, const luadnsrule_t& var, const std::shared_ptr& action, boost::optional& params) { setLuaSideEffect(); @@ -1819,4 +1850,8 @@ void setupLuaActions() parseResponseConfig(vars, action->d_responseConfig); return ret; }); + + g_lua.writeFunction("SetProxyProtocolValuesAction", [](const std::vector>& values) { + return std::shared_ptr(new SetProxyProtocolValuesAction(values)); + }); } diff --git a/pdns/dnsdistdist/docs/reference/dq.rst b/pdns/dnsdistdist/docs/reference/dq.rst index 4259a260f8..d758b942f5 100644 --- a/pdns/dnsdistdist/docs/reference/dq.rst +++ b/pdns/dnsdistdist/docs/reference/dq.rst @@ -208,7 +208,7 @@ This state can be modified from the various hooks. .. versionadded:: 1.5.0 - Set the Type-Length Values to send to the backend using the Proxy Protocol. + Set the Proxy-Protocol Type-Length values to send to the backend along with this query. :param table values: A table of types and values to send, for example: ``{ ["0"] = foo", ["42"] = "bar" }`` diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index c534787af7..4ff2fcb57d 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1251,6 +1251,14 @@ The following actions exist. * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query. +.. function:: SetProxyProtocolValuesAction(values) + + .. versionadded:: 1.5.0 + + Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to ``values``. + + :param table values: A table of types and values to send, for example: ``{ ["0"] = foo", ["42"] = "bar" }`` + .. function:: SkipCacheAction() Don't lookup the cache for this query, don't store the answer. diff --git a/regression-tests.dnsdist/test_ProxyProtocol.py b/regression-tests.dnsdist/test_ProxyProtocol.py index 955e5d9cb8..2d4e0069ec 100644 --- a/regression-tests.dnsdist/test_ProxyProtocol.py +++ b/regression-tests.dnsdist/test_ProxyProtocol.py @@ -168,6 +168,7 @@ class TestProxyProtocol(ProxyProtocolTest): end addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues)) + addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"})) """ _config_params = ['_proxyResponderPort'] @@ -300,3 +301,68 @@ class TestProxyProtocol(ProxyProtocolTest): self.assertEquals(receivedQuery, query) self.assertEquals(receivedResponse, response) self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'] , [ 42, b'bar'] ]) + + def testProxyUDPWithValuesFromAction(self): + """ + Proxy Protocol: values from Action (UDP) + """ + name = 'values-action.proxy.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + + toProxyQueue.put(response, True, 2.0) + + data = query.to_wire() + self._sock.send(data) + receivedResponse = None + try: + self._sock.settimeout(2.0) + data = self._sock.recv(4096) + except socket.timeout: + print('timeout') + data = None + if data: + receivedResponse = dns.message.from_wire(data) + + (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0) + self.assertTrue(receivedProxyPayload) + self.assertTrue(receivedDNSData) + self.assertTrue(receivedResponse) + + receivedQuery = dns.message.from_wire(receivedDNSData) + receivedQuery.id = query.id + receivedResponse.id = response.id + self.assertEquals(receivedQuery, query) + self.assertEquals(receivedResponse, response) + self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ]) + + def testProxyTCPWithValuesFromAction(self): + """ + Proxy Protocol: values from Action (TCP) + """ + name = 'values-action.proxy.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + + toProxyQueue.put(response, True, 2.0) + + conn = self.openTCPConnection(2.0) + data = query.to_wire() + self.sendTCPQueryOverConnection(conn, data, rawQuery=True) + receivedResponse = None + try: + receivedResponse = self.recvTCPResponseOverConnection(conn) + except socket.timeout: + print('timeout') + + (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0) + self.assertTrue(receivedProxyPayload) + self.assertTrue(receivedDNSData) + self.assertTrue(receivedResponse) + + receivedQuery = dns.message.from_wire(receivedDNSData) + receivedQuery.id = query.id + receivedResponse.id = response.id + self.assertEquals(receivedQuery, query) + self.assertEquals(receivedResponse, response) + self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ]) -- 2.47.2