]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Pad if needed when making a response. (#1026)
authorBob Halley <halley@dnspython.org>
Tue, 26 Dec 2023 19:12:39 +0000 (11:12 -0800)
committerGitHub <noreply@github.com>
Tue, 26 Dec 2023 19:12:39 +0000 (11:12 -0800)
dns/message.py
doc/rfc.rst
tests/test_message.py

index 8887f73be45f8d8363a4916bd405400ecf40c98b..21eaaf2f0e03f88336b4a03756dfc760bfdc8355 100644 (file)
@@ -826,6 +826,8 @@ class Message:
             if request_payload is None:
                 request_payload = payload
             self.request_payload = request_payload
+            if pad < 0:
+                raise ValueError("pad must be non-negative")
             self.pad = pad
 
     @property
@@ -1817,30 +1819,34 @@ def make_response(
     our_payload: int = 8192,
     fudge: int = 300,
     tsig_error: int = 0,
+    pad: Optional[int] = None,
 ) -> Message:
     """Make a message which is a response for the specified query.
-    The message returned is really a response skeleton; it has all
-    of the infrastructure required of a response, but none of the
-    content.
+    The message returned is really a response skeleton; it has all of the infrastructure
+    required of a response, but none of the content.
 
-    The response's question section is a shallow copy of the query's
-    question section, so the query's question RRsets should not be
-    changed.
+    The response's question section is a shallow copy of the query's question section,
+    so the query's question RRsets should not be changed.
 
     *query*, a ``dns.message.Message``, the query to respond to.
 
     *recursion_available*, a ``bool``, should RA be set in the response?
 
-    *our_payload*, an ``int``, the payload size to advertise in EDNS
-    responses.
+    *our_payload*, an ``int``, the payload size to advertise in EDNS responses.
 
     *fudge*, an ``int``, the TSIG time fudge.
 
     *tsig_error*, an ``int``, the TSIG error.
 
-    Returns a ``dns.message.Message`` object whose specific class is
-    appropriate for the query.  For example, if query is a
-    ``dns.update.UpdateMessage``, response will be too.
+    *pad*, a non-negative ``int`` or ``None``.  If 0, the default, do not pad; otherwise
+    if not ``None`` add padding bytes to make the message size a multiple of *pad*.
+    Note that if padding is non-zero, an EDNS PADDING option will always be added to the
+    message.  If ``None``, add padding following RFC 8467, namely if the request is
+    padded, pad the response to 468 otherwise do not pad.
+
+    Returns a ``dns.message.Message`` object whose specific class is appropriate for the
+    query.  For example, if query is a ``dns.update.UpdateMessage``, response will be
+    too.
     """
 
     if query.flags & dns.flags.QR:
@@ -1853,7 +1859,13 @@ def make_response(
     response.set_opcode(query.opcode())
     response.question = list(query.question)
     if query.edns >= 0:
-        response.use_edns(0, 0, our_payload, query.payload)
+        if pad is None:
+            # Set response padding per RFC 8467
+            pad = 0
+            for option in query.options:
+                if option.otype == dns.edns.OptionType.PADDING:
+                    pad = 468
+        response.use_edns(0, 0, our_payload, query.payload, pad=pad)
     if query.had_tsig:
         response.use_tsig(
             query.keyring,
index 7e4da5d41924dcb439962bae14087d83b9c54be4..27afc261d04f1af1e8ff1b813badfeb6f97dfd8d 100644 (file)
@@ -60,12 +60,19 @@ Core RFCs
 `RFC 6891 <https://tools.ietf.org/html/rfc6891>`_
     EDNS (version 0)
 
+`RFC 7830 <https://tools.ietf.org/html/rfc7830.html>`_
+    The EDNS(0) Padding Option
+
 `RFC 8020 <https://tools.ietf.org/html/rfc8020>`_
     Clarification on the meaning of NXDOMAIN.
 
+`RFC 8467 <https://tools.ietf.org/html/rfc8467>`_
+    Padding Policies for Extension Mechanisms for DNS (EDNS(0))
+
 `RFC 8914 <https://tools.ietf.org/html/rfc8914.html>`_
     Extended DNS Errors
 
+
 DNSSEC RFCs
 -----------
 
index 0ee53983de849f1f620c3b9f678c5def397dfdfa..93c8aafd0f5f898e0718c4a1c87ea5787a77bb08 100644 (file)
 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-import unittest
 import binascii
+import unittest
 
-import dns.exception
 import dns.edns
+import dns.exception
 import dns.flags
 import dns.message
 import dns.name
 import dns.rdataclass
 import dns.rdatatype
-import dns.rrset
-import dns.tsig
-import dns.update
 import dns.rdtypes.ANY.OPT
 import dns.rdtypes.ANY.TSIG
+import dns.rrset
+import dns.tsig
 import dns.tsigkeyring
-
+import dns.update
 from tests.util import here
 
 query_text = """id 1234
@@ -882,6 +881,24 @@ www.dnspython.org. 300 IN A 1.2.3.4
         self.assertIsNotNone(q2.tsig)
         self.assertEqual(q, q2)
 
+    def test_response_padding(self):
+        q = dns.message.make_query("www.example", "a", use_edns=0, pad=128)
+        w = q.to_wire()
+        self.assertEqual(len(w), 128)
+        # We need to go read the wire as the padding isn't instantiated in q.
+        pq = dns.message.from_wire(w)
+        r = dns.message.make_response(pq)
+        assert r.pad == 468
+        r = dns.message.make_response(pq, pad=0)
+        assert r.pad == 0
+        r = dns.message.make_response(pq, pad=40)
+        assert r.pad == 40
+        q = dns.message.make_query("www.example", "a", use_edns=0, pad=0)
+        w = q.to_wire()
+        pq = dns.message.from_wire(w)
+        r = dns.message.make_response(pq)
+        assert r.pad == 0
+
     def test_prefer_truncation_answer(self):
         q = dns.message.make_query("www.example", "a")
         rrs = [