]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add setProxyProtocolValuesAction()
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Feb 2020 15:28:15 +0000 (16:28 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 17 Mar 2020 13:12:55 +0000 (14:12 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_ProxyProtocol.py

index 6fe525e2ddc4f71acc77fd576a2729a31180e443..b1ec405a10e9f1b104c983038ad118fd78d827ae 100644 (file)
@@ -576,6 +576,7 @@ const std::vector<ConsoleKeyword> 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"},
index 82ae55ffe344ad774302e6a65303964e4c1c6b5d..c3e1e11360f3e896d20cb0976470807177c405e8 100644 (file)
@@ -1418,6 +1418,37 @@ private:
   bool d_nxd;
 };
 
+class SetProxyProtocolValuesAction : public DNSAction
+{
+public:
+  SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& 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<std::vector<ProxyProtocolValue>>();
+    }
+
+    *(dq->proxyProtocolValues) = d_values;
+
+    return Action::None;
+  }
+
+  std::string toString() const override
+  {
+    return "set Proxy-Protocol values";
+  }
+
+private:
+  std::vector<ProxyProtocolValue> d_values;
+};
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
   setLuaSideEffect();
@@ -1819,4 +1850,8 @@ void setupLuaActions()
       parseResponseConfig(vars, action->d_responseConfig);
       return ret;
     });
+
+  g_lua.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
+      return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
+    });
 }
index 4259a260f8fe88562cff0b975a0dbd19cadc38c7..d758b942f5a6d2b3afc6fe16be73751c06c85af3 100644 (file)
@@ -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" }``
 
index c534787af791e4dccbd8e9fb42cf987d46546151..4ff2fcb57df2b8977b18830a8a50df2dd104d522 100644 (file)
@@ -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.
index 955e5d9cb8956e51de0f1875296ed950dfa79da9..2d4e0069ec58e35e01e99ae27774334489de0c81 100644 (file)
@@ -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'] ])