mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 18:20:00 -04:00
Use isctest.asyncserver in the "ixfr" system test
Replace the usage of the `bin/tests/system/ans.pl` server with an instance of ControllableAsyncServer.
This commit is contained in:
parent
2302fe1235
commit
46ecbbed0a
4 changed files with 276 additions and 88 deletions
265
bin/tests/system/ixfr/ans2/ans.py
Normal file
265
bin/tests/system/ixfr/ans2/ans.py
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
See the COPYRIGHT file distributed with this work for additional
|
||||
information regarding copyright ownership.
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from typing import AsyncGenerator, Collection, Iterable
|
||||
|
||||
from isctest.asyncserver import (
|
||||
ControllableAsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
QueryContext,
|
||||
ResponseHandler,
|
||||
SwitchControlCommand,
|
||||
)
|
||||
|
||||
|
||||
def rrset(owner: str, rdtype: dns.rdatatype.RdataType, rdata: str) -> dns.rrset.RRset:
|
||||
return dns.rrset.from_text(
|
||||
owner,
|
||||
300,
|
||||
dns.rdataclass.IN,
|
||||
rdtype,
|
||||
rdata,
|
||||
)
|
||||
|
||||
|
||||
def soa(serial: int, *, owner: str = "nil.") -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
owner,
|
||||
dns.rdatatype.SOA,
|
||||
f"ns.nil. root.nil. {serial} 300 300 604800 300",
|
||||
)
|
||||
|
||||
|
||||
def ns() -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
"nil.",
|
||||
dns.rdatatype.NS,
|
||||
"ns.nil.",
|
||||
)
|
||||
|
||||
|
||||
def a(address: str, *, owner: str) -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
owner,
|
||||
dns.rdatatype.A,
|
||||
address,
|
||||
)
|
||||
|
||||
|
||||
def txt(data: str, *, owner: str = "nil.") -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
owner,
|
||||
dns.rdatatype.TXT,
|
||||
f'"{data}"',
|
||||
)
|
||||
|
||||
|
||||
class SoaHandler(ResponseHandler):
|
||||
def __init__(self, serial: int):
|
||||
self._serial = serial
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.SOA
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.answer.append(soa(self._serial))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class AxfrHandler(ResponseHandler):
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def answers(self) -> Iterable[Collection[dns.rrset.RRset]]:
|
||||
"""
|
||||
Answer sections of response packets sent in response to
|
||||
AXFR queries.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.AXFR
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
for answer in self.answers:
|
||||
response = qctx.prepare_new_response()
|
||||
for rrset_ in answer:
|
||||
response.answer.append(rrset_)
|
||||
yield DnsResponseSend(response)
|
||||
|
||||
|
||||
class IxfrHandler(ResponseHandler):
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def answer(self) -> Collection[dns.rrset.RRset]:
|
||||
"""
|
||||
Answer section of a response packet sent in response to
|
||||
IXFR queries.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.IXFR
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
for rrset_ in self.answer:
|
||||
qctx.response.answer.append(rrset_)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class InitialAfxrHandler(AxfrHandler):
|
||||
answers = (
|
||||
(soa(1),),
|
||||
(
|
||||
ns(),
|
||||
txt("initial AXFR"),
|
||||
a("10.0.0.61", owner="a.nil."),
|
||||
a("10.0.0.62", owner="b.nil."),
|
||||
),
|
||||
(soa(1),),
|
||||
)
|
||||
|
||||
|
||||
class SuccessfulIfxrHandler(IxfrHandler):
|
||||
answer = (
|
||||
soa(3),
|
||||
soa(1),
|
||||
a("10.0.0.61", owner="a.nil."),
|
||||
txt("initial AXFR"),
|
||||
soa(2),
|
||||
txt("successful IXFR"),
|
||||
a("10.0.1.61", owner="a.nil."),
|
||||
soa(2),
|
||||
soa(3),
|
||||
soa(3),
|
||||
)
|
||||
|
||||
|
||||
class NotExactIxfrHandler(IxfrHandler):
|
||||
answer = (
|
||||
soa(4),
|
||||
soa(3),
|
||||
txt("delete-nonexistent-txt-record"),
|
||||
soa(4),
|
||||
txt("this-txt-record-would-be-added"),
|
||||
soa(4),
|
||||
)
|
||||
|
||||
|
||||
class FallbackNotExactAxfrHandler(AxfrHandler):
|
||||
answers = (
|
||||
(soa(3),),
|
||||
(
|
||||
ns(),
|
||||
txt("fallback AXFR"),
|
||||
),
|
||||
(soa(3),),
|
||||
)
|
||||
|
||||
|
||||
class TooManyRecordsIxfrHandler(IxfrHandler):
|
||||
answer = (
|
||||
soa(4),
|
||||
soa(3),
|
||||
soa(4),
|
||||
txt("text 1"),
|
||||
txt("text 2"),
|
||||
txt("text 3"),
|
||||
txt("text 4"),
|
||||
txt("text 5"),
|
||||
txt("text 6: causing too many records"),
|
||||
soa(4),
|
||||
)
|
||||
|
||||
|
||||
class FallbackTooManyRecordsAxfrHandler(AxfrHandler):
|
||||
answers = (
|
||||
(
|
||||
soa(3),
|
||||
ns(),
|
||||
txt("fallback AXFR on too many records"),
|
||||
),
|
||||
(soa(3),),
|
||||
)
|
||||
|
||||
|
||||
class BadSoaOwnerIxfrHandler(IxfrHandler):
|
||||
answer = (
|
||||
soa(4),
|
||||
soa(3),
|
||||
soa(4, owner="bad-owner."),
|
||||
txt("serial 4, malformed IXFR", owner="test.nil."),
|
||||
soa(4),
|
||||
)
|
||||
|
||||
|
||||
class FallbackBadSoaOwnerAxfrHandler(AxfrHandler):
|
||||
answers = (
|
||||
(soa(4),),
|
||||
(
|
||||
ns(),
|
||||
txt("serial 4, fallback AXFR", owner="test.nil."),
|
||||
),
|
||||
(soa(4),),
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = ControllableAsyncDnsServer(
|
||||
default_aa=True, default_rcode=dns.rcode.NOERROR
|
||||
)
|
||||
switch_command = SwitchControlCommand(
|
||||
{
|
||||
"initial_axfr": (
|
||||
SoaHandler(1),
|
||||
InitialAfxrHandler(),
|
||||
),
|
||||
"successful_ixfr": (
|
||||
SoaHandler(3),
|
||||
SuccessfulIfxrHandler(),
|
||||
),
|
||||
"not_exact": (
|
||||
SoaHandler(4),
|
||||
NotExactIxfrHandler(),
|
||||
FallbackNotExactAxfrHandler(),
|
||||
),
|
||||
"too_many_records": (
|
||||
SoaHandler(4),
|
||||
TooManyRecordsIxfrHandler(),
|
||||
FallbackTooManyRecordsAxfrHandler(),
|
||||
),
|
||||
"bad_soa_owner": (
|
||||
SoaHandler(4),
|
||||
BadSoaOwnerIxfrHandler(),
|
||||
FallbackBadSoaOwnerAxfrHandler(),
|
||||
),
|
||||
}
|
||||
)
|
||||
server.install_control_command(switch_command)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -32,27 +32,16 @@ n=0
|
|||
DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}"
|
||||
RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../_common/rndc.conf -s"
|
||||
|
||||
sendcmd() {
|
||||
send 10.53.0.2 "${EXTRAPORT1}"
|
||||
switch_responses() {
|
||||
RESPONSES_KEY="${1}"
|
||||
$DIG $DIGOPTS "@10.53.0.2" "${RESPONSES_KEY}.switch._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1
|
||||
}
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "testing initial AXFR ($n)"
|
||||
ret=0
|
||||
|
||||
sendcmd <<EOF
|
||||
/SOA/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "initial AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
b.nil. 60 A 10.0.0.62
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
EOF
|
||||
switch_responses "initial_axfr"
|
||||
|
||||
sleep 1
|
||||
|
||||
|
|
@ -84,21 +73,7 @@ ret=0
|
|||
# We change the IP address of a.nil., and the TXT record at the apex.
|
||||
# Then we do a SOA-only update.
|
||||
|
||||
sendcmd <<EOF
|
||||
/SOA/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
/IXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
a.nil. 60 A 10.0.0.61
|
||||
nil. 300 TXT "initial AXFR"
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
nil. 300 TXT "successful IXFR"
|
||||
a.nil. 60 A 10.0.1.61
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
EOF
|
||||
switch_responses "successful_ixfr"
|
||||
|
||||
sleep 1
|
||||
|
||||
|
|
@ -115,25 +90,7 @@ echo_i "testing AXFR fallback after IXFR failure (not exact error) ($n)"
|
|||
ret=0
|
||||
|
||||
# Provide a broken IXFR response and a working fallback AXFR response
|
||||
|
||||
sendcmd <<EOF
|
||||
/SOA/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/IXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
nil. 300 TXT "delete-nonexistent-txt-record"
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
nil. 300 TXT "this-txt-record-would-be-added"
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "fallback AXFR"
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
EOF
|
||||
switch_responses "not_exact"
|
||||
|
||||
sleep 1
|
||||
|
||||
|
|
@ -150,28 +107,7 @@ echo_i "testing AXFR fallback after IXFR failure (too many records) ($n)"
|
|||
ret=0
|
||||
|
||||
# Provide an IXFR response that would cause a "too many records" condition
|
||||
|
||||
sendcmd <<EOF
|
||||
/SOA/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/IXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
nil. 300 TXT "text 1"
|
||||
nil. 300 TXT "text 2"
|
||||
nil. 300 TXT "text 3"
|
||||
nil. 300 TXT "text 4"
|
||||
nil. 300 TXT "text 5"
|
||||
nil. 300 TXT "text 6: causing too many records"
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "fallback AXFR on too many records"
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
EOF
|
||||
switch_responses "too_many_records"
|
||||
|
||||
sleep 1
|
||||
|
||||
|
|
@ -196,23 +132,7 @@ ret=0
|
|||
nextpart ns1/named.run >/dev/null
|
||||
|
||||
# Provide a broken IXFR response and a working fallback AXFR response.
|
||||
sendcmd <<EOF
|
||||
/SOA/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/IXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
bad-owner. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
test.nil. 300 TXT "serial 4, malformed IXFR"
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 NS ns.nil.
|
||||
test.nil. 300 TXT "serial 4, fallback AXFR"
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
EOF
|
||||
switch_responses "bad_soa_owner"
|
||||
$RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i
|
||||
|
||||
# A broken server would accept the malformed IXFR and apply its contents to the
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
import pytest
|
||||
|
||||
# isctest.asyncserver requires dnspython >= 2.0.0
|
||||
pytest.importorskip("dns", minversion="2.0.0")
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"dig.out*",
|
||||
|
|
|
|||
Loading…
Reference in a new issue