Skip to content

Commit bc3009d

Browse files
authored
Honor raise_on_serial_went_backwards on both inbound_xfr paths (#1298)
dns.query.inbound_xfr() forwarded the flag on its TCP path but not its UDP one, and dns.asyncquery.inbound_xfr() did the opposite, so each implementation honored the option on exactly one of its two paths. For the async version that is the default path, since udp_mode defaults to UDPMode.NEVER. _inbound_xfr()'s default for the parameter is dropped as well: it is private, all five call sites now pass the flag, and the default is what turned the two missed call sites into silent no-ops rather than errors. Co-authored-by: Dylan Pulver <dylanpulver@users.noreply.github.com>
1 parent 90add95 commit bc3009d

3 files changed

Lines changed: 136 additions & 4 deletions

File tree

dns/asyncquery.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ async def _inbound_xfr(
850850
serial: int | None,
851851
timeout: float | None,
852852
expiration: float | None,
853-
raise_on_serial_went_backwards: bool = True,
853+
raise_on_serial_went_backwards: bool,
854854
) -> Any:
855855
"""Given a socket, does the zone transfer."""
856856
rdtype = query.question[0].rdtype
@@ -960,6 +960,12 @@ async def inbound_xfr(
960960
)
961961
async with s:
962962
async for _ in _inbound_xfr( # pyright: ignore
963-
txn_manager, s, query, serial, timeout, expiration # pyright: ignore
963+
txn_manager,
964+
s,
965+
query,
966+
serial,
967+
timeout,
968+
expiration, # pyright: ignore
969+
raise_on_serial_went_backwards,
964970
):
965971
pass

dns/query.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,7 +1500,7 @@ def _inbound_xfr(
15001500
serial: int | None,
15011501
timeout: float | None,
15021502
expiration: float | None,
1503-
raise_on_serial_went_backwards: bool = True,
1503+
raise_on_serial_went_backwards: bool,
15041504
) -> Any:
15051505
"""Given a socket, does the zone transfer."""
15061506
rdtype = query.question[0].rdtype
@@ -1723,7 +1723,13 @@ def inbound_xfr(
17231723
_connect(s, destination, expiration)
17241724
try:
17251725
for _ in _inbound_xfr(
1726-
txn_manager, s, query, serial, timeout, expiration
1726+
txn_manager,
1727+
s,
1728+
query,
1729+
serial,
1730+
timeout,
1731+
expiration,
1732+
raise_on_serial_went_backwards,
17271733
):
17281734
pass
17291735
return

tests/test_xfr.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,3 +851,123 @@ def old_runner(awaitable):
851851

852852
runner = old_runner
853853
runner(run())
854+
855+
856+
#
857+
# The master's serial is older than ours, in a single-message IXFR response.
858+
#
859+
went_backwards_ixfr = """id 1
860+
opcode QUERY
861+
rcode NOERROR
862+
flags AA
863+
;QUESTION
864+
example. IN IXFR
865+
;ANSWER
866+
@ 3600 IN SOA foo bar 5 2 3 4 5
867+
"""
868+
869+
# Newer than what went_backwards_ixfr offers, so an IXFR from it goes backwards.
870+
went_backwards_base = """@ 3600 IN SOA foo bar 6 2 3 4 5
871+
@ 3600 IN NS ns1
872+
"""
873+
874+
875+
class WentBackwardsNanoNameserver(Server):
876+
def __init__(self):
877+
super().__init__(origin=dns.name.from_text("example"))
878+
879+
def handle(self, request):
880+
try:
881+
r = dns.message.from_text(
882+
went_backwards_ixfr, one_rr_per_rrset=True, origin=self.origin
883+
)
884+
r.id = request.message.id
885+
return r
886+
except Exception:
887+
pass
888+
889+
890+
def went_backwards_zone():
891+
return dns.zone.from_text(
892+
went_backwards_base, "example", zone_factory=dns.versioned.Zone
893+
)
894+
895+
896+
def check_went_backwards_zone(zone):
897+
# Declining to raise ends the transfer without applying anything, so the
898+
# zone must be untouched.
899+
assert zone == dns.zone.from_text(went_backwards_base, "example")
900+
901+
902+
#
903+
# raise_on_serial_went_backwards has to reach dns.xfr.Inbound on both the UDP
904+
# and the TCP path. UDPMode.NEVER is TCP only; TRY_FIRST sends the IXFR over
905+
# UDP first.
906+
#
907+
udp_modes = [dns.query.UDPMode.NEVER, dns.query.UDPMode.TRY_FIRST]
908+
909+
910+
@pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver")
911+
@pytest.mark.parametrize("udp_mode", udp_modes)
912+
def test_sync_inbound_xfr_serial_went_backwards(udp_mode):
913+
with WentBackwardsNanoNameserver() as ns:
914+
where, port = ns.tcp_address
915+
with pytest.raises(dns.xfr.SerialWentBackwards):
916+
dns.query.inbound_xfr(
917+
where, went_backwards_zone(), port=port, udp_mode=udp_mode
918+
)
919+
zone = went_backwards_zone()
920+
dns.query.inbound_xfr(
921+
where,
922+
zone,
923+
port=port,
924+
udp_mode=udp_mode,
925+
raise_on_serial_went_backwards=False,
926+
)
927+
check_went_backwards_zone(zone)
928+
929+
930+
async def async_inbound_xfr_serial_went_backwards(udp_mode):
931+
with WentBackwardsNanoNameserver() as ns:
932+
where, port = ns.tcp_address
933+
with pytest.raises(dns.xfr.SerialWentBackwards):
934+
await dns.asyncquery.inbound_xfr(
935+
where, went_backwards_zone(), port=port, udp_mode=udp_mode
936+
)
937+
zone = went_backwards_zone()
938+
await dns.asyncquery.inbound_xfr(
939+
where,
940+
zone,
941+
port=port,
942+
udp_mode=udp_mode,
943+
raise_on_serial_went_backwards=False,
944+
)
945+
check_went_backwards_zone(zone)
946+
947+
948+
@pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver")
949+
@pytest.mark.parametrize("udp_mode", udp_modes)
950+
def test_asyncio_inbound_xfr_serial_went_backwards(udp_mode):
951+
dns.asyncbackend.set_default_backend("asyncio")
952+
953+
async def run():
954+
await async_inbound_xfr_serial_went_backwards(udp_mode)
955+
956+
asyncio.run(run())
957+
958+
959+
try:
960+
import trio
961+
962+
@pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver")
963+
@pytest.mark.parametrize("udp_mode", udp_modes)
964+
def test_trio_inbound_xfr_serial_went_backwards(udp_mode):
965+
dns.asyncbackend.set_default_backend("trio")
966+
967+
async def run():
968+
await async_inbound_xfr_serial_went_backwards(udp_mode)
969+
970+
trio.run(run)
971+
972+
except ImportError:
973+
pass

0 commit comments

Comments
 (0)