Home | History | Annotate | Line # | Download | only in mismatchtcp
      1  1.1  christos # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      2  1.1  christos #
      3  1.1  christos # SPDX-License-Identifier: MPL-2.0
      4  1.1  christos #
      5  1.1  christos # This Source Code Form is subject to the terms of the Mozilla Public
      6  1.1  christos # License, v. 2.0.  If a copy of the MPL was not distributed with this
      7  1.1  christos # file, you can obtain one at https://mozilla.org/MPL/2.0/.
      8  1.1  christos #
      9  1.1  christos # See the COPYRIGHT file distributed with this work for additional
     10  1.1  christos # information regarding copyright ownership.
     11  1.1  christos 
     12  1.1  christos """
     13  1.1  christos End-to-end check for the immediate UDP-to-TCP fallback on a query-id
     14  1.1  christos mismatch.
     15  1.1  christos 
     16  1.1  christos The fake authoritative server at 10.53.0.2 answers every UDP query for
     17  1.1  christos trigger.example./A with a response whose DNS message id has been flipped.
     18  1.1  christos The resolver at 10.53.0.1 must escalate to TCP on the first such response
     19  1.1  christos and return the correct A record that the fake server serves over TCP.
     20  1.1  christos """
     21  1.1  christos 
     22  1.1  christos from pathlib import Path
     23  1.1  christos 
     24  1.1  christos import dns.message
     25  1.1  christos import dns.rdatatype
     26  1.1  christos import pytest
     27  1.1  christos 
     28  1.1  christos import isctest
     29  1.1  christos 
     30  1.1  christos pytestmark = pytest.mark.extra_artifacts(
     31  1.1  christos     [
     32  1.1  christos         "ans*/ans.run",
     33  1.1  christos         "ns*/named.stats*",
     34  1.1  christos     ]
     35  1.1  christos )
     36  1.1  christos 
     37  1.1  christos 
     38  1.1  christos MISMATCH_LABEL = "mismatch responses received"
     39  1.1  christos MISMATCHTCP_LABEL = "queries retried over TCP after a response with mismatched query id"
     40  1.1  christos 
     41  1.1  christos 
     42  1.1  christos def _named_stats(ns1) -> str:
     43  1.1  christos     stats_path = Path(ns1.directory) / "named.stats"
     44  1.1  christos     if stats_path.exists():
     45  1.1  christos         stats_path.unlink()
     46  1.1  christos     ns1.rndc("stats")
     47  1.1  christos     return stats_path.read_text(encoding="utf-8")
     48  1.1  christos 
     49  1.1  christos 
     50  1.1  christos def _counter(stats: str, label: str) -> int:
     51  1.1  christos     for line in stats.splitlines():
     52  1.1  christos         line = line.strip()
     53  1.1  christos         if line.endswith(label):
     54  1.1  christos             return int(line.split()[0])
     55  1.1  christos     return 0
     56  1.1  christos 
     57  1.1  christos 
     58  1.1  christos def test_mismatch_tcp_fallback(ns1):
     59  1.1  christos     """
     60  1.1  christos     Issue a single recursive query for a name whose UDP responses are
     61  1.1  christos     being spoofed.  The resolver must escalate to TCP on the first
     62  1.1  christos     near-miss and return the correct A record.
     63  1.1  christos     """
     64  1.1  christos     msg = dns.message.make_query("trigger.example.", dns.rdatatype.A, want_dnssec=False)
     65  1.1  christos     res = isctest.query.udp(msg, ns1.ip, timeout=10)
     66  1.1  christos     isctest.check.noerror(res)
     67  1.1  christos 
     68  1.1  christos     answers = [rrset for rrset in res.answer if rrset.rdtype == dns.rdatatype.A]
     69  1.1  christos     assert answers, f"no A RRset in response: {res}"
     70  1.1  christos     addresses = {item.address for rrset in answers for item in rrset}
     71  1.1  christos     assert "192.0.2.42" in addresses, f"unexpected answer: {addresses}"
     72  1.1  christos 
     73  1.1  christos 
     74  1.1  christos def test_mismatch_counter(ns1):
     75  1.1  christos     """
     76  1.1  christos     After the spoofed exchange completes the resolver's existing
     77  1.1  christos     "mismatch responses received" counter must be non-zero, confirming
     78  1.1  christos     the dispatcher actually saw the wrong-id response, and the new
     79  1.1  christos     "queries retried over TCP after a response with mismatched query
     80  1.1  christos     id" counter must also be non-zero, confirming that the TCP
     81  1.1  christos     fallback path actually fired in response to that mismatch.
     82  1.1  christos     """
     83  1.1  christos     msg = dns.message.make_query("trigger.example.", dns.rdatatype.A, want_dnssec=False)
     84  1.1  christos     isctest.query.udp(msg, ns1.ip, timeout=10)
     85  1.1  christos 
     86  1.1  christos     stats = _named_stats(ns1)
     87  1.1  christos     assert _counter(stats, MISMATCH_LABEL) > 0, stats
     88  1.1  christos     assert _counter(stats, MISMATCHTCP_LABEL) > 0, stats
     89