--- /dev/null
+Asynchronous processing
+=======================
+
+Since 1.8.0, dnsdist has the ability to process queries and responses in an asynchronous way, suspending them to continue processing other queries and responses, while we are waiting for an external event to occur.
+
+This is done by calling the :meth:`DNSQuestion:suspend` method on a query or a response to pause it, then later the :func:`getAsynchronousObject` to retrieve it before resuming via :meth:`AsynchronousObject:resume`.
+
+A timeout must be supplied when pausing a query or a response, to prevent paused objects from piling up, consuming memory. When the timeout expires, the suspended object is automatically retrieved and resumes its processing where it was left.
+
+.. figure:: ../imgs/AsyncQuery.png
+ :align: center
+ :alt: Asynchronous processing of queries and responses
+
+The following code shows a very simple example that forwards queries and responses to an external component over a unix network socket, and resumes them when it gets an answer from the external component.
+
+.. code-block:: lua
+
+ local asyncID = 0
+ local asyncResponderEndpoint = newNetworkEndpoint('/path/to/unix/network/socket/remote/endpoint')
+ local listener = newNetworkListener()
+ listener:addUnixListeningEndpoint('/path/to/unix/network/socket/local/endpoint', 0, gotAsyncResponse)
+ listener:start()
+
+ function gotAsyncResponse(endpointID, message, from)
+ local queryID = tonumber(message)
+ local asyncObject = getAsynchronousObject(asyncID, queryID)
+ local dq = asyncObject:getDQ()
+ dq:setTag(filteringTagName, filteringTagValue)
+ asyncObject:resume()
+ end
+
+ function passQueryToAsyncFilter(dq)
+ local timeout = 500 -- 500 ms
+ local buffer = dq:getContent()
+ local id = dq.dh:getID()
+ dq:suspend(asyncID, id, timeout)
+ asyncResponderEndpoint:send(buffer)
+ return DNSAction.Allow
+ end
+
+ function passResponseToAsyncFilter(dr)
+ local timeout = 500 -- 500 ms
+ local buffer = dr:getContent()
+ local id = dr.dh:getID()
+ dr:suspend(asyncID, id, timeout)
+ asyncResponderEndpoint:send(buffer)
+ return DNSResponseAction.Allow
+ end
+
+ addAction(AllRule(), LuaAction(passQueryToAsyncFilter))
+ addCacheHitResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
+ addResponseAction(AllRule(), LuaResponseAction(passResponseToAsyncFilter))
:param string reason: An optional string describing the reason why this trap was sent
+ .. method:: DNSQuestion:setContent(data)
+
+ .. versionadded:: 1.8.0
+
+ Replace the DNS payload of the query with the supplied data, and adjusts the ID in the DNS header to the existing query.
+
+ :param string data: The raw DNS payload
+
.. method:: DNSQuestion:setEDNSOption(code, data)
.. versionadded:: 1.8.0
:param string raw: The raw string to be spoofed, e.g. `"\\192\\000\\002\\001"`.
:param table raws: The raw strings to be spoofed, e.g. `{ "\\192\\000\\002\\001", "\\192\\000\\002\\002" }`.
+ .. method:: DNSQuestion:suspend(asyncID, queryID, timeoutMS) -> bool
+
+ .. versionadded:: 1.8.0
+
+ Suspend the processing for the current query, making it asynchronous. The query is then placed into memory, in a map called the Asynchronous Holder, until it is either resumed or the supplied timeout kicks in. The object is stored under a key composed of the tuple (`asyncID`, `queryID`) which is needed to retrieve it later, which can be done via :func:`getAsynchronousObject`.
+ Note that the DNSQuestion object should NOT be accessed after successfully calling this method.
+ Returns true on success and false on failure, indicating that the query has not been suspended and the normal processing will continue.
+
+ :param int asyncID: A numeric identifier used to identify the suspended query for later retrieval
+ :param int queryID: A numeric identifier used to identify the suspended query for later retrieval. This ID does not have to match the query ID present in the initial DNS header. A given (asyncID, queryID) tuple should be unique at a given time.
+ :param int timeoutMS: The maximum duration this query will be kept in the asynchronous holder before being automatically resumed, in milliseconds.
+
.. _DNSResponse:
DNSResponse object
Get checking disabled flag.
+ .. method:: DNSHeader:getID() -> int
+
+ .. versionadded:: 1.8.0
+
+ Get the ID.
+
.. method:: DNSHeader:getRA() -> bool
Get recursion available flag.
.. method:: EDNSOptionView:getValues()
Return a table of NULL-safe strings values for this EDNS option.
+
+.. _AsynchronousObject:
+
+AsynchronousObject object
+=========================
+
+.. class:: AsynchronousObject
+
+ .. versionadded:: 1.8.0
+
+ This object holds a representation of a DNS query or response that has been suspended.
+
+ .. method:: AsynchronousObject:drop() -> bool
+
+ Drop that object immediately, without resuming it.
+ Returns true on success, false on failure.
+
+ .. method:: AsynchronousObject:getDQ() -> DNSQuestion
+
+ Return a DNSQuestion object for the suspended object.
+
+ .. method:: AsynchronousObject:getDR() -> DNSResponse
+
+ Return a DNSResponse object for the suspended object.
+
+ .. method:: AsynchronousObject:resume() -> bool
+
+ Resume the processing of the suspended object.
+ For a question, it means first checking whether it was turned into a response,
+ and sending the response out it it was. Otherwise do a cache-lookup, select a
+ backend and send the query to the backend.
+ For a response, it means inserting into the cache if needed and sending the
+ response to the backend.
+ Note that the AsynchronousObject object should NOT be accessed after successfully calling this method.
+ Returns true on success, false on failure.
+
+ .. method:: AsynchronousObject:setRCode(rcode, clearRecords) -> bool
+
+ Set the response code in the DNS header of the current object to the supplied value,
+ optionally removing all records from the existing payload, if any.
+ Returns true on success, false on failure.
+
+ :param int code: The response code to set
+ :param bool clearRecords: Whether to clear all records from the existing payload, if any
+
+.. function:: getAsynchronousObject(asyncID, queryID) -> AsynchronousObject
+
+ .. versionadded:: 1.8.0
+
+ Retrieves an asynchronous object stored into the Asynchronous holder.
+
+ :param int asyncID: A numeric identifier used to identify the query when it was suspended
+ :param int queryID: A numeric identifier used to identify the query when it was suspended