1#!/usr/bin/python3
2
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# SPDX-License-Identifier: MPL-2.0
6#
7# This Source Code Form is subject to the terms of the Mozilla Public
8# License, v. 2.0.  If a copy of the MPL was not distributed with this
9# file, you can obtain one at https://mozilla.org/MPL/2.0/.
10#
11# See the COPYRIGHT file distributed with this work for additional
12# information regarding copyright ownership.
13
14import os
15import re
16import subprocess
17
18import pytest
19
20pytest.importorskip("dns", minversion="2.0.0")
21import dns.resolver
22
23
24def run_rndc(server, rndc_command):
25    """
26    Send the specified 'rndc_command' to 'server' with a timeout of 10 seconds
27    """
28    rndc = os.getenv("RNDC")
29    port = os.getenv("CONTROLPORT")
30
31    cmdline = [rndc, "-c", "../_common/rndc.conf", "-p", port, "-s", server]
32    cmdline.extend(rndc_command)
33
34    subprocess.check_output(cmdline, stderr=subprocess.STDOUT, timeout=10)
35
36
37def test_dnstap_dispatch_socket_addresses(named_port):
38    # Prepare for querying ns3.
39    resolver = dns.resolver.Resolver()
40    resolver.nameservers = ["10.53.0.3"]
41    resolver.port = named_port
42
43    # Send some query to ns3 so that it records something in its dnstap file.
44    ans = resolver.resolve("mail.example.", "A")
45    assert ans[0].address == "10.0.0.2"
46
47    # Before continuing, roll dnstap file to ensure it is flushed to disk.
48    run_rndc("10.53.0.3", ["dnstap", "-roll", "1"])
49
50    # Move the dnstap file aside so that it is retained for troubleshooting.
51    os.rename(os.path.join("ns3", "dnstap.out.0"), "dnstap.out.resolver_addresses")
52
53    # Read the contents of the dnstap file using dnstap-read.
54    output = subprocess.check_output(
55        [os.getenv("DNSTAPREAD"), "dnstap.out.resolver_addresses"], encoding="utf-8"
56    )
57
58    # Check whether all frames contain the expected addresses.
59    #
60    # Expected dnstap-read output format:
61    #
62    #     22-Jun-2022 12:09:06.168 RR 10.53.0.3:0 -> 10.53.0.1:7523 TCP ...
63    #     22-Jun-2022 12:09:06.168 RR 10.53.0.3:0 <- 10.53.0.1:7523 TCP ...
64    #     22-Jun-2022 12:09:06.168 RQ 10.53.0.3:56306 -> 10.53.0.2:7523 UDP ...
65    #     22-Jun-2022 12:09:06.168 RQ 10.53.0.3:56306 <- 10.53.0.2:7523 UDP ...
66    #
67    bad_frames = []
68    inspected_frames = 0
69    addr_regex = r"^10\.53\.0\.[0-9]+:[0-9]{1,5}$"
70    for line in output.splitlines():
71        _, _, frame_type, addr1, _, addr2, _ = line.split(" ", 6)
72        # Only inspect RESOLVER_QUERY and RESOLVER_RESPONSE frames.
73        if frame_type not in ("RQ", "RR"):
74            continue
75        inspected_frames += 1
76        if not re.match(addr_regex, addr1) or not re.match(addr_regex, addr2):
77            bad_frames.append(line)
78
79    assert (
80        len(bad_frames) == 0
81    ), "{} out of {} inspected frames contain unexpected addresses:\n\n{}".format(
82        len(bad_frames), inspected_frames, "\n".join(bad_frames)
83    )
84