]> granicus.if.org Git - pdns/commitdiff
recursor: add new deviceName field to the dnsmessage protobuf export
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 19 Jun 2019 09:48:42 +0000 (11:48 +0200)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Fri, 28 Jun 2019 16:56:32 +0000 (18:56 +0200)
pdns/dnsmessage.proto
pdns/lua-recursor4.cc
pdns/lua-recursor4.hh
pdns/pdns_recursor.cc
pdns/protobuf.cc
pdns/protobuf.hh
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/lua-scripting/hooks.rst
regression-tests.recursor-dnssec/test_Protobuf.py

index a460b51f3cd09ca2f404252dcdad0f0a7ccf06e2..fa8970d7d03f267d83f46b9966d0736bb15e976b 100644 (file)
@@ -87,4 +87,5 @@ message PBDNSMessage {
   optional bytes initialRequestId = 16;         // UUID of the incoming query that initiated this outgoing query or incoming response
   optional bytes deviceId = 17;                // Device ID of the requestor (could be mac address IP address or e.g. IMEI)
   optional bool  newlyObservedDomain = 18;      // True if the domain has not been seen before
+  optional string deviceName = 19;              // Device name of the requestor
 }
index 4ad23d22460b0688afdbd6283887225ec45fc657..98b38c8bfb528e0f123bd1966a7c5a2dd1562f17 100644 (file)
@@ -533,7 +533,7 @@ bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& loca
   return false; // don't block
 }
 
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId) const
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const
 {
   if(d_gettag) {
     auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp);
@@ -558,6 +558,11 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
     if (deviceIdret) {
       deviceId = *deviceIdret;
     }
+
+    const auto deviceNameret = std::get<5>(ret);
+    if (deviceNameret) {
+      deviceName = *deviceNameret;
+    }
     return std::get<0>(ret);
   }
   return 0;
@@ -566,7 +571,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
 struct pdns_ffi_param
 {
 public:
-  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::vector<std::string>& policyTags_, const EDNSOptionViewMap& ednsOptions_, std::string& requestorId_, std::string& deviceId_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), ednsOptions(ednsOptions_), requestorId(requestorId_), deviceId(deviceId_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), qtype(qtype_), tcp(tcp_)
+  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::vector<std::string>& policyTags_, const EDNSOptionViewMap& ednsOptions_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), ednsOptions(ednsOptions_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), qtype(qtype_), tcp(tcp_)
   {
   }
 
@@ -584,6 +589,7 @@ public:
   const EDNSOptionViewMap& ednsOptions;
   std::string& requestorId;
   std::string& deviceId;
+  std::string& deviceName;
   uint32_t& ttlCap;
   bool& variable;
   bool& logQuery;
@@ -593,10 +599,10 @@ public:
   bool tcp;
 };
 
-unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, uint32_t& ttlCap, bool& variable, bool& logQuery) const
+unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, uint32_t& ttlCap, bool& variable, bool& logQuery) const
 {
   if (d_gettag_ffi) {
-    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, ednsOptions, requestorId, deviceId, ttlCap, variable, tcp, logQuery);
+    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, ednsOptions, requestorId, deviceId, deviceName, ttlCap, variable, tcp, logQuery);
 
     auto ret = d_gettag_ffi(&param);
     if (ret) {
@@ -843,7 +849,7 @@ void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name)
 
 void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name)
 {
-  ref->deviceId = std::string(name);
+  ref->deviceName = std::string(name);
 }
 
 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name)
index 20e7f2bc53307bde746872780ca8f49319a03d6c..7a8a2539b9a62f038af8a87af307b4c0467e5518 100644 (file)
@@ -77,6 +77,7 @@ public:
     std::unordered_map<std::string,bool>* discardedPolicies{nullptr};
     std::string requestorId;
     std::string deviceId;
+    std::string deviceName;
     vState validationState{Indeterminate};
     bool& variable;
     bool& wantsRPZ;
@@ -110,8 +111,8 @@ public:
     DNSName followupName;
   };
 
-  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId) const;
-  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, uint32_t& ttlCap, bool& variable, bool& logQuery) const;
+  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const;
+  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, uint32_t& ttlCap, bool& variable, bool& logQuery) const;
 
   void maintenance() const;
   bool prerpz(DNSQuestion& dq, int& ret) const;
@@ -132,7 +133,7 @@ public:
             d_postresolve);
   }
 
-  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool)> gettag_t;
+  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<std::string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool)> gettag_t;
   gettag_t d_gettag; // public so you can query if we have this hooked
   typedef std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)> gettag_ffi_t;
   gettag_ffi_t d_gettag_ffi;
index 491ae26bd95d34a0c4e3a5c62b2130bd90c0e4cc..24f6c77b73a9872113e7ddf850ad4bc6ec38b76a 100644 (file)
@@ -321,6 +321,7 @@ struct DNSComboWriter {
   boost::uuids::uuid d_uuid;
   string d_requestorId;
   string d_deviceId;
+  string d_deviceName;
   struct timeval d_kernelTimestamp{0,0};
 #endif
   std::string d_query;
@@ -795,7 +796,7 @@ catch(...)
 }
 
 #ifdef HAVE_PROTOBUF
-static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::vector<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId)
+static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::vector<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName)
 {
   if (!t_protobufServers) {
     return;
@@ -808,6 +809,7 @@ static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids:
   message.setEDNSSubnet(ednssubnet, ednssubnet.isIpv4() ? maskV4 : maskV6);
   message.setRequestorId(requestorId);
   message.setDeviceId(deviceId);
+  message.setDeviceName(deviceName);
 
   if (!policyTags.empty()) {
     message.setPolicyTags(policyTags);
@@ -1246,6 +1248,7 @@ static void startDoResolve(void *p)
 #ifdef HAVE_PROTOBUF
     dq.requestorId = dc->d_requestorId;
     dq.deviceId = dc->d_deviceId;
+    dq.deviceName = dc->d_deviceName;
 #endif
 
     if(ednsExtRCode != 0) {
@@ -1627,6 +1630,7 @@ static void startDoResolve(void *p)
       }
       pbMessage->setRequestorId(dq.requestorId);
       pbMessage->setDeviceId(dq.deviceId);
+      pbMessage->setDeviceName(dq.deviceName);
 #ifdef NOD_ENABLED
       if (g_nodEnabled) {
         if (nod) {
@@ -1987,6 +1991,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       bool needXPF = g_XPFAcl.match(conn->d_remote);
       string requestorId;
       string deviceId;
+      string deviceName;
       bool logQuery = false;
 #ifdef HAVE_PROTOBUF
       auto luaconfsLocal = g_luaconfs.getLocal();
@@ -2014,10 +2019,10 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
           if(t_pdl) {
             try {
               if (t_pdl->d_gettag_ffi) {
-                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, dc->d_ttlCap, dc->d_variable, logQuery);
+                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_ttlCap, dc->d_variable, logQuery);
               }
               else if (t_pdl->d_gettag) {
-                dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId);
+                dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName);
               }
             }
             catch(const std::exception& e)  {
@@ -2039,6 +2044,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       if(t_protobufServers || t_outgoingProtobufServers) {
         dc->d_requestorId = requestorId;
         dc->d_deviceId = deviceId;
+        dc->d_deviceName = deviceName;
         dc->d_uuid = getUniqueID();
       }
 
@@ -2046,7 +2052,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         try {
 
           if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && dc->d_policyTags.empty())) {
-              protobufLogQuery(luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId);
+            protobufLogQuery(luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName);
           }
         }
         catch(std::exception& e) {
@@ -2180,6 +2186,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   ComboAddress destination = destaddr;
   string requestorId;
   string deviceId;
+  string deviceName;
   bool logQuery = false;
 #ifdef HAVE_PROTOBUF
   boost::uuids::uuid uniqueId;
@@ -2238,10 +2245,10 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
         if(t_pdl) {
           try {
             if (t_pdl->d_gettag_ffi) {
-              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, ttlCap, variable, logQuery);
+              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, ttlCap, variable, logQuery);
             }
             else if (t_pdl->d_gettag) {
-              ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId);
+              ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName);
             }
           }
           catch(const std::exception& e)  {
@@ -2264,7 +2271,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
       pbMessage = RecProtoBufMessage(DNSProtoBufMessage::DNSProtoBufMessageType::Response);
       pbMessage->setServerIdentity(SyncRes::s_serverID);
       if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
-        protobufLogQuery(luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId);
+        protobufLogQuery(luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName);
       }
     }
 #endif /* HAVE_PROTOBUF */
@@ -2302,6 +2309,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
         }
         pbMessage->setRequestorId(requestorId);
         pbMessage->setDeviceId(deviceId);
+        pbMessage->setDeviceName(deviceName);
         protobufLogResponse(*pbMessage);
       }
 #endif /* HAVE_PROTOBUF */
@@ -2377,6 +2385,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   }
   dc->d_requestorId = requestorId;
   dc->d_deviceId = deviceId;
+  dc->d_deviceName = deviceName;
   dc->d_kernelTimestamp = tv;
 #endif
 
index fd491ac669f629cb407f3bd3d73c71aac75b0510..46f625fa705ebefe6e094734fbc95a6c992c08e6 100644 (file)
@@ -242,6 +242,13 @@ void DNSProtoBufMessage::setDeviceId(const std::string& deviceId)
 #endif /* HAVE_PROTOBUF */
 }
 
+void DNSProtoBufMessage::setDeviceName(const std::string& deviceName)
+{
+#ifdef HAVE_PROTOBUF
+  d_message.set_devicename(deviceName);
+#endif /* HAVE_PROTOBUF */
+}
+
 void DNSProtoBufMessage::setServerIdentity(const std::string& serverId)
 {
 #ifdef HAVE_PROTOBUF
index f87dd5f08cc6a57b10f128a7946573363b40c4a1..0f1f74a157dc15503aac86c57f611682d04e1318 100644 (file)
@@ -70,6 +70,7 @@ public:
   void setResponder(const ComboAddress& responder);
   void setRequestorId(const std::string& requestorId);
   void setDeviceId(const std::string& deviceId);
+  void setDeviceName(const std::string& deviceName);
   void setServerIdentity(const std::string& serverId);
   std::string toDebugString() const;
   void addTag(const std::string& strValue);
index dc9a1fcdef9dc1089277788e56e2be7678e03e4c..591bc7f5212f5b1accc59e93256c7f1cee971ef7 100644 (file)
@@ -108,6 +108,12 @@ The DNSQuestion object contains at least the following fields:
 
       A string that will be used to set the ``deviceId`` field in :doc:`protobuf <../lua-config/protobuf>` messages.
 
+  .. attribute:: DNSQuestion.deviceName
+
+      .. versionadded:: 4.1.15
+
+      A string that will be used to set the ``deviceName`` field in :doc:`protobuf <../lua-config/protobuf>` messages.
+
   .. attribute:: DNSQuestion.udpAnswer
 
       Answer to the :attr:`udpQuery <DNSQuestion.udpQuery>` when when using the ``udpQueryResponse`` :attr:`followupFunction <DNSQuestion.followupFunction>`.
index 887d8d1de15c8ac06444540cd29e6b5c039c5dd9..ea04d7b8d8f1f3e901dcef012a9e9cbc0703a092 100644 (file)
@@ -69,6 +69,9 @@ Interception Functions
     .. versionadded:: 4.1.0
 
         It can also return a table whose keys and values are strings to fill the :attr:`DNSQuestion.data` table, as well as a ``requestorId`` value to fill the :attr:`DNSQuestion.requestorId` field and a ``deviceId`` value to fill the :attr:`DNSQuestion.deviceId` field.
+    .. versionadded:: 4.1.15
+
+        Along the ``deviceId`` value that can be returned, it was addded a ``deviceName`` field to fill the :attr:`DNSQuestion.deviceName` field.
 
     The tagged packetcache can e.g. be used to answer queries from cache that have e.g. been filtered for certain IPs (this logic should be implemented in :func:`gettag`).
     This ensure that queries are answered quickly compared to setting :attr:`dq.variable <DNSQuestion.variable>` to true.
index 50a6449d4b45487175a9d1813e3d973eefd0ed67..c1ff6a4bc98e1e9e659826d20f17f6ed96b0e52d 100644 (file)
@@ -221,6 +221,14 @@ class TestRecursorProtobuf(RecursorTest):
         self.assertTrue(msg.HasField('response'))
         self.assertTrue(msg.response.HasField('queryTimeSec'))
 
+    def checkProtobufIdentity(self, msg, requestorId, deviceId, deviceName):
+        self.assertTrue(msg.HasField('requestorId'))
+        self.assertTrue(msg.HasField('deviceId'))
+        self.assertTrue(msg.HasField('deviceName'))
+        self.assertEquals(msg.requestorId, requestorId)
+        self.assertEquals(msg.deviceId, deviceId)
+        self.assertEquals(msg.deviceName, deviceName)
+
     @classmethod
     def setUpClass(cls):
 
@@ -652,3 +660,112 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
                 self.assertEquals(rr.rdata, 'a.example.')
 
         self.checkNoRemainingMessage()
+
+class ProtobufTaggedExtraFieldsTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export extra fields that may have been set while being tagged.
+    """
+
+    _confdir = 'ProtobufTaggedExtraFields'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+    _lua_config_file = """
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+    _requestorId = 'S-000001727'
+    _deviceId = 'd1:0a:91:dc:cc:82'
+    _deviceName = 'Joe'
+    _lua_dns_script_file = """
+    function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
+      if qname:equal('tagged.example.') then
+        -- tag number, policy tags, data, requestorId, deviceId, deviceName
+        return 0, {}, {}, '%s', '%s', '%s'
+      end
+      return 0
+    end
+    """ % (_requestorId, _deviceId, _deviceName)
+
+    def testA(self):
+        name = 'a.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf message corresponding to the UDP response
+        # the first query and answer are not tagged, so there is nothing in the queue
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+        self.checkProtobufIdentity(msg, '', '', '')
+
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
+        self.assertEquals(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkProtobufIdentity(msg, '', '', '')
+        self.checkNoRemainingMessage()
+
+    def testTagged(self):
+        name = 'tagged.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+        self.checkProtobufIdentity(msg, self._requestorId, self._deviceId, self._deviceName)
+
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+        self.assertEquals(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+        self.checkProtobufIdentity(msg, self._requestorId, self._deviceId, self._deviceName)
+        self.checkNoRemainingMessage()
+
+class ProtobufTaggedExtraFieldsFFITest(ProtobufTaggedExtraFieldsTest):
+    """
+    This test makes sure that we correctly export extra fields that may have been set while being tagged (FFI version).
+    """
+    _confdir = 'ProtobufTaggedExtraFieldsFFI'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+    _lua_config_file = """
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+    _lua_dns_script_file = """
+    local ffi = require("ffi")
+
+    ffi.cdef[[
+      typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+      const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
+      void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
+      void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name);
+      void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name);
+      void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name);
+    ]]
+
+    function gettag_ffi(obj)
+      qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
+      if qname == 'tagged.example' then
+        ffi.C.pdns_ffi_param_set_requestorid(obj, "%s")
+        deviceid = "%s"
+        ffi.C.pdns_ffi_param_set_deviceid(obj, string.len(deviceid), deviceid)
+        ffi.C.pdns_ffi_param_set_devicename(obj, "%s")
+      end
+      return 0
+    end
+    """ % (ProtobufTaggedExtraFieldsTest._requestorId, ProtobufTaggedExtraFieldsTest._deviceId, ProtobufTaggedExtraFieldsTest._deviceName)