mirror of
https://github.com/isc-projects/bind9.git
synced 2026-07-16 15:32:51 -04:00
Reimplement the FORMERR system test in Python
Replace the shell and Perl based FORMERR system test with a Python
test that constructs the malformed DNS packets directly and checks
the responses.
Remove the legacy shell and Perl test script and the intermediate
packet files in hex, leaving the packet construction inline in
tests_formerr.py.
Preserve the same wire for all packets sent to the server, but
construct them in a more explicit and readable way.
(cherry picked from commit 151b2d6045)
This commit is contained in:
parent
95eb81bf1c
commit
28e0c79b12
23 changed files with 525 additions and 465 deletions
|
|
@ -1,7 +0,0 @@
|
|||
# bad NSEC3 owner (:) not in base32hex valid characters
|
||||
# header: id=0008 opcode=query questions=1 authority=1
|
||||
00 08 00 00 00 01 00 00 00 01 00 00
|
||||
# question: ./A/IN
|
||||
00 00 01 00 01
|
||||
# :./NSEC3/IN/1 length=7 hashtype=240 flags=0 interations=0 salt=- hashlen=1 hash=ff
|
||||
01 58 00 00 32 00 01 00 00 00 01 00 07 f0 00 00 00 00 01 ff
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# oversized owner name
|
||||
# header: additional=1
|
||||
00 00 00 00 00 00 00 00 00 00 00 01
|
||||
# owner name too big (256 octets) A/IN/1 0.0.0.0
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01 00 01 00 00 00 01 00 04 00 00 00 00
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# multiple singletons (SOA)
|
||||
# header questions=1 answers=2
|
||||
00 00 00 00 00 01 00 02 00 00 00 00
|
||||
# question SOA/IN
|
||||
00 00 06 00 01
|
||||
# 2 SOA records that differ in expire
|
||||
00 00 06 00 01 00 00 00 01 00 16 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05
|
||||
00 00 06 00 01 00 00 00 01 00 16 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 06
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# header: 2 questions
|
||||
00 00 00 00 00 02 00 00 00 00 00 00
|
||||
# question: AAAAAAAAAAAAAA./A/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01
|
||||
00 01
|
||||
# question: AAAAAAAAAAAAAA./A/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01
|
||||
00 01
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# 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.
|
||||
|
||||
# This is a tool for sending an arbitrary packet via UDP or TCP to an
|
||||
# arbitrary address and port. The packet is specified in a file or on
|
||||
# the standard input, in the form of a series of bytes in hexadecimal.
|
||||
# Whitespace is ignored, as is anything following a '#' symbol.
|
||||
#
|
||||
# For example, the following input would generate normal query for
|
||||
# isc.org/NS/IN":
|
||||
#
|
||||
# # QID:
|
||||
# 0c d8
|
||||
# # header:
|
||||
# 01 00 00 01 00 00 00 00 00 00
|
||||
# # qname isc.org:
|
||||
# 03 69 73 63 03 6f 72 67 00
|
||||
# # qtype NS:
|
||||
# 00 02
|
||||
# # qclass IN:
|
||||
# 00 01
|
||||
#
|
||||
# Note that we do not wait for a response for the server. This is simply
|
||||
# a way of injecting arbitrary packets to test server resposnes.
|
||||
#
|
||||
# Usage: packet.pl [-a <address>] [-p <port>] [-t (udp|tcp)] [filename]
|
||||
#
|
||||
# If not specified, address defaults to 127.0.0.1, port to 53, protocol
|
||||
# to udp, and file to stdin.
|
||||
#
|
||||
# XXX: Doesn't support IPv6 yet
|
||||
|
||||
require 5.006_001;
|
||||
|
||||
use strict;
|
||||
use Getopt::Std;
|
||||
use IO::File;
|
||||
use IO::Socket;
|
||||
|
||||
sub usage {
|
||||
print ("Usage: packet.pl [-a address] [-p port] [file]\n");
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my %options={};
|
||||
getopts("a:p:", \%options);
|
||||
|
||||
my $addr = "127.0.0.1";
|
||||
$addr = $options{a} if defined $options{a};
|
||||
|
||||
my $port = 53;
|
||||
$port = $options{p} if defined $options{p};
|
||||
|
||||
my $file = "STDIN";
|
||||
if (@ARGV >= 1) {
|
||||
my $filename = shift @ARGV;
|
||||
open FH, "<$filename" or die "$filename: $!";
|
||||
$file = "FH";
|
||||
}
|
||||
|
||||
my $input = "";
|
||||
while (defined(my $line = <$file>) ) {
|
||||
chomp $line;
|
||||
$line =~ s/#.*$//;
|
||||
$input .= $line;
|
||||
}
|
||||
|
||||
$input =~ s/\s+//g;
|
||||
my $data = pack("H*", $input);
|
||||
my $len = length $data;
|
||||
|
||||
my $output = unpack("H*", $data);
|
||||
print ("sending: $output\n");
|
||||
|
||||
my $sock = IO::Socket::INET->new(PeerAddr => $addr, PeerPort => $port,
|
||||
Proto => "tcp") or die "$!";
|
||||
|
||||
my $bytes;
|
||||
$bytes = $sock->syswrite(pack("n", $len), 2);
|
||||
$bytes = $sock->syswrite($data, $len);
|
||||
$bytes = $sock->sysread($data, 2);
|
||||
$len = unpack("n", $data);
|
||||
$bytes = $sock->sysread($data, $len);
|
||||
print "got: ", unpack("H*", $data). "\n";
|
||||
|
||||
$sock->close;
|
||||
close $file;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# mismatched key class
|
||||
# header: questions=1 additional=1
|
||||
00 00 00 00 00 01 00 00 00 00 00 01
|
||||
# question: ./A/IN
|
||||
00 00 01 00 01
|
||||
# additional: ./KEY/CLASS2 flags=0 protocol=0 algorithm=248 keydata=00
|
||||
00 00 19 00 02 00 00 00 01 00 05 00 00 00 f8 00
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# UPDATE malformed 'delete type' update change (non empty data)
|
||||
# header: UPDATE authority=1
|
||||
00 00 28 00 00 00 00 00 00 01 00 00
|
||||
# ./A/ANY TTL=0 length=1 data=00
|
||||
00 00 01 00 ff 00 00 00 00 00 01 00
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# malformed RRRSIG
|
||||
# header: QUERY, additional=1
|
||||
00 00 00 00 00 00 00 00 00 00 00 01
|
||||
# ./RRSIG/IN TTL=1 covers=0 algorithm=240 labels=0 ttl=1 expire=2 signed=3 id=0
|
||||
00 00 2e 00 01 00 00 00 01 00 14 00 00 f0 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 00
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
00 00 00 00 00 01 00 00 00 00 00 00
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0f 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01
|
||||
00 01
|
||||
|
|
@ -1 +0,0 @@
|
|||
00 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# OPT record with wrong name (not .)
|
||||
# header: QUERY, additional=1
|
||||
00 00 00 00 00 00 00 00 00 00 00 01
|
||||
# OPT record (owner A.)
|
||||
01 41 00 00 29 00 01 00 00 00 00 00 00
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# QTYPE-only type as answer
|
||||
# header, answers=1
|
||||
00 00 00 00 00 00 00 01 00 00 00 00
|
||||
# ./MAILB/IN
|
||||
00 00 fd 00 01 00 00 00 01 00 00
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# two questions of different classes
|
||||
# header: QUERY, questions=2
|
||||
00 00 00 00 00 02 00 00 00 00 00 00
|
||||
# ./A/IN
|
||||
00 00 01 00 01
|
||||
# ./A/CLASS2
|
||||
00 00 01 00 02
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# truncated question section
|
||||
# header: QUERY, questions=1
|
||||
00 00 00 00 00 01 00 00 00 00 00 00
|
||||
# truncated question (no class)
|
||||
00 00 01
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# truncated record
|
||||
# header: QUERY, additional=1
|
||||
00 09 00 00 00 00 00 00 00 00 00 01
|
||||
# truncated A record (no ttl, length or data)
|
||||
00 00 01 00 01
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# 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.
|
||||
|
||||
set -e
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
status=0
|
||||
|
||||
echo_i "test name too long"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} nametoolong >nametoolong.out
|
||||
ans=$(grep got: nametoolong.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
|
||||
echo_i "two question names"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} twoquestionnames >twoquestionnames.out
|
||||
ans=$(grep got: twoquestionnames.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
|
||||
echo_i "two question types"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} twoquestiontypes >twoquestiontypes.out
|
||||
ans=$(grep got: twoquestiontypes.out)
|
||||
if [ "${ans}" != "got: 0000800100020000000000000e41414141414141414141414141410000010001c00c00020001" ]; then
|
||||
echo_i "failed"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
|
||||
echo_i "duplicate questions"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} dupquestion >dupquestion.out
|
||||
ans=$(grep got: dupquestion.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "duplicate answer"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} dupans >dupans.out
|
||||
ans=$(grep got: dupans.out)
|
||||
if [ "${ans}" != "got: 0000800100010000000000000000060001" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "question only type in answer"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} qtypeasanswer >qtypeasanswer.out
|
||||
ans=$(grep got: qtypeasanswer.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
# this would be NOERROR if it included a COOKIE option,
|
||||
# but is a FORMERR without one.
|
||||
echo_i "empty question section (and no COOKIE option)"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} noquestions >noquestions.out
|
||||
ans=$(grep got: noquestions.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
|
||||
echo_i "bad nsec3 owner"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} badnsec3owner >badnsec3owner.out
|
||||
ans=$(grep got: badnsec3owner.out)
|
||||
# SERVFAIL (2) rather than FORMERR (1)
|
||||
if [ "${ans}" != "got: 0008800200010000000000000000010001" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "short record before rdata "
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} shortrecord >shortrecord.out
|
||||
ans=$(grep got: shortrecord.out)
|
||||
if [ "${ans}" != "got: 000980010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "short question"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} shortquestion >shortquestion.out
|
||||
ans=$(grep got: shortquestion.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "mismatch classes in question section"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} questionclass >questionclass.out
|
||||
ans=$(grep got: questionclass.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "bad record owner name"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} badrecordname >badrecordname.out
|
||||
ans=$(grep got: badrecordname.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "mismatched class in record"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} wrongclass >wrongclass.out
|
||||
ans=$(grep got: wrongclass.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "mismatched KEY class"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} keyclass >keyclass.out
|
||||
ans=$(grep got: keyclass.out)
|
||||
if [ "${ans}" != "got: 0000800100010000000000000000010001" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "OPT wrong owner name"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} optwrongname >optwrongname.out
|
||||
ans=$(grep got: optwrongname.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "RRSIG invalid covers"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} malformedrrsig >malformedrrsig.out
|
||||
ans=$(grep got: malformedrrsig.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "UPDATE malformed delete type"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} malformeddeltype >malformeddeltype.out
|
||||
ans=$(grep got: malformeddeltype.out)
|
||||
if [ "${ans}" != "got: 0000a8010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "TSIG wrong class"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} tsigwrongclass >tsigwrongclass.out
|
||||
ans=$(grep got: tsigwrongclass.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "TSIG not last"
|
||||
$PERL formerr.pl -a 10.53.0.1 -p ${PORT} tsignotlast >tsignotlast.out
|
||||
ans=$(grep got: tsignotlast.out)
|
||||
if [ "${ans}" != "got: 000080010000000000000000" ]; then
|
||||
echo_i "failed"
|
||||
status=$(expr $status + 1)
|
||||
fi
|
||||
|
||||
echo_i "exit status: $status"
|
||||
|
||||
[ $status -eq 0 ] || exit 1
|
||||
525
bin/tests/system/formerr/tests_formerr.py
Normal file
525
bin/tests/system/formerr/tests_formerr.py
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
# 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.
|
||||
|
||||
from typing import SupportsInt
|
||||
|
||||
import socket
|
||||
|
||||
import dns.flags
|
||||
import dns.name
|
||||
import dns.opcode
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rdtypes.ANY.NSEC3
|
||||
import dns.rdtypes.ANY.OPT
|
||||
import dns.rdtypes.ANY.RRSIG
|
||||
import dns.rdtypes.ANY.SOA
|
||||
import dns.rdtypes.ANY.TSIG
|
||||
import dns.rdtypes.IN.A
|
||||
import pytest
|
||||
|
||||
|
||||
def wire(*parts: bytes) -> bytes:
|
||||
return b"".join(parts)
|
||||
|
||||
|
||||
def u16(value: SupportsInt) -> bytes:
|
||||
return int(value).to_bytes(2, byteorder="big")
|
||||
|
||||
|
||||
def u32(value: SupportsInt) -> bytes:
|
||||
return int(value).to_bytes(4, byteorder="big")
|
||||
|
||||
|
||||
def name(text: str) -> bytes:
|
||||
return dns.name.from_text(text).to_wire()
|
||||
|
||||
|
||||
def name_pointer(offset: int) -> bytes:
|
||||
return u16(0xC000 | offset)
|
||||
|
||||
|
||||
def root() -> bytes:
|
||||
return dns.name.root.to_wire()
|
||||
|
||||
|
||||
def header(
|
||||
message_id: int = 0,
|
||||
flags: int = 0,
|
||||
opcode: int = dns.opcode.QUERY,
|
||||
rcode: dns.rcode.Rcode = dns.rcode.NOERROR,
|
||||
qdcount: int = 0,
|
||||
ancount: int = 0,
|
||||
nscount: int = 0,
|
||||
arcount: int = 0,
|
||||
) -> bytes:
|
||||
return wire(
|
||||
u16(message_id),
|
||||
u16(flags | dns.opcode.to_flags(opcode) | int(rcode)),
|
||||
u16(qdcount),
|
||||
u16(ancount),
|
||||
u16(nscount),
|
||||
u16(arcount),
|
||||
)
|
||||
|
||||
|
||||
def formerr_response_header(
|
||||
message_id: int = 0,
|
||||
opcode: int = dns.opcode.QUERY,
|
||||
rcode: dns.rcode.Rcode = dns.rcode.FORMERR,
|
||||
qdcount: int = 0,
|
||||
ancount: int = 0,
|
||||
nscount: int = 0,
|
||||
arcount: int = 0,
|
||||
) -> bytes:
|
||||
return header(
|
||||
message_id=message_id,
|
||||
flags=dns.flags.QR,
|
||||
opcode=opcode,
|
||||
rcode=rcode,
|
||||
qdcount=qdcount,
|
||||
ancount=ancount,
|
||||
nscount=nscount,
|
||||
arcount=arcount,
|
||||
)
|
||||
|
||||
|
||||
def question(
|
||||
qname: bytes,
|
||||
qtype: dns.rdatatype.RdataType = dns.rdatatype.RdataType.A,
|
||||
qclass: dns.rdataclass.RdataClass = dns.rdataclass.RdataClass.IN,
|
||||
) -> bytes:
|
||||
return wire(
|
||||
qname,
|
||||
u16(qtype),
|
||||
u16(qclass),
|
||||
)
|
||||
|
||||
|
||||
def rr(
|
||||
owner: bytes,
|
||||
rrtype: SupportsInt,
|
||||
rrclass: SupportsInt,
|
||||
*,
|
||||
ttl: int,
|
||||
rdata: bytes = b"",
|
||||
) -> bytes:
|
||||
return wire(
|
||||
question(
|
||||
owner, dns.rdatatype.RdataType(rrtype), dns.rdataclass.RdataClass(rrclass)
|
||||
),
|
||||
u32(ttl),
|
||||
u16(len(rdata)),
|
||||
rdata,
|
||||
)
|
||||
|
||||
|
||||
def oversized_name() -> bytes:
|
||||
labels = [bytes([15]) + b"A" * 15 for _ in range(15)]
|
||||
labels.append(bytes([14]) + b"A" * 14 + root())
|
||||
return wire(*labels)
|
||||
|
||||
|
||||
def soa_rr(
|
||||
*,
|
||||
minimum: int,
|
||||
) -> bytes:
|
||||
return rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.SOA,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=1,
|
||||
rdata=dns.rdtypes.ANY.SOA.SOA(
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
dns.rdatatype.RdataType.SOA,
|
||||
dns.name.root,
|
||||
dns.name.root,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
minimum,
|
||||
).to_wire(),
|
||||
)
|
||||
|
||||
|
||||
def nsec3_rr(
|
||||
*,
|
||||
owner: bytes,
|
||||
) -> bytes:
|
||||
return rr(
|
||||
owner,
|
||||
dns.rdatatype.RdataType.NSEC3,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=1,
|
||||
rdata=dns.rdtypes.ANY.NSEC3.NSEC3(
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
dns.rdatatype.RdataType.NSEC3,
|
||||
algorithm=240,
|
||||
flags=0,
|
||||
iterations=0,
|
||||
salt=b"",
|
||||
next=b"\xff",
|
||||
windows=[],
|
||||
).to_wire(),
|
||||
)
|
||||
|
||||
|
||||
def key_rdata(
|
||||
*,
|
||||
flags: int,
|
||||
protocol: int,
|
||||
algorithm: int,
|
||||
keydata: bytes,
|
||||
) -> bytes:
|
||||
# No dns.rdtypes.ANY.KEY.KEY class, so construct the rdata manually
|
||||
return wire(u16(flags), bytes([protocol, algorithm]), keydata)
|
||||
|
||||
|
||||
def key_rr(*, rdclass: dns.rdataclass.RdataClass) -> bytes:
|
||||
return rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.KEY,
|
||||
rdclass,
|
||||
ttl=1,
|
||||
rdata=key_rdata(
|
||||
flags=0,
|
||||
protocol=0,
|
||||
algorithm=248,
|
||||
keydata=b"\x00",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def malformed_rrsig_rr() -> bytes:
|
||||
return rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.RRSIG,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=1,
|
||||
rdata=dns.rdtypes.ANY.RRSIG.RRSIG(
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
dns.rdatatype.RdataType.RRSIG,
|
||||
0,
|
||||
240,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
dns.name.root,
|
||||
b"\x00",
|
||||
).to_wire(),
|
||||
)
|
||||
|
||||
|
||||
def tsig_rr(
|
||||
*,
|
||||
owner: bytes = root(),
|
||||
rdclass: dns.rdataclass.RdataClass = dns.rdataclass.RdataClass.ANY,
|
||||
algorithm: dns.name.Name = dns.name.root,
|
||||
time_signed: int = 0x010203040506,
|
||||
fudge: int = 0x0102,
|
||||
mac: bytes = b"\x00",
|
||||
original_id: int = 0,
|
||||
error: int = 0,
|
||||
other: bytes = b"",
|
||||
) -> bytes:
|
||||
return rr(
|
||||
owner,
|
||||
dns.rdatatype.RdataType.TSIG,
|
||||
rdclass,
|
||||
ttl=1,
|
||||
rdata=dns.rdtypes.ANY.TSIG.TSIG(
|
||||
rdclass,
|
||||
dns.rdatatype.RdataType.TSIG,
|
||||
algorithm,
|
||||
time_signed,
|
||||
fudge,
|
||||
mac,
|
||||
original_id,
|
||||
error,
|
||||
other,
|
||||
).to_wire(),
|
||||
)
|
||||
|
||||
|
||||
def opt_rr(*, owner: bytes) -> bytes:
|
||||
return rr(
|
||||
owner,
|
||||
dns.rdatatype.RdataType.OPT,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=0,
|
||||
rdata=dns.rdtypes.ANY.OPT.OPT(
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
dns.rdatatype.RdataType.OPT,
|
||||
[],
|
||||
).to_wire(),
|
||||
)
|
||||
|
||||
|
||||
def a_rdata(ipv4_bytes: bytes = b"\x00\x00\x00\x00") -> bytes:
|
||||
return dns.rdtypes.IN.A.A(
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
dns.rdatatype.RdataType.A,
|
||||
ipv4_bytes,
|
||||
).to_wire()
|
||||
|
||||
|
||||
def a_rr(owner: bytes = root()) -> bytes:
|
||||
return rr(
|
||||
owner,
|
||||
dns.rdatatype.RdataType.A,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=1,
|
||||
rdata=a_rdata(),
|
||||
)
|
||||
|
||||
|
||||
def query_raw_tcp(host: str, port: int, packet_wire: bytes) -> bytes:
|
||||
with (
|
||||
socket.create_connection((host, port), timeout=10) as sock,
|
||||
sock.makefile("rwb") as f,
|
||||
):
|
||||
f.write(u16(len(packet_wire)))
|
||||
f.write(packet_wire)
|
||||
f.flush()
|
||||
length = int.from_bytes(f.read(2), byteorder="big")
|
||||
return f.read(length)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"query_wire,expected_wire",
|
||||
[
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=1),
|
||||
question(oversized_name()),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="nametoolong",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=2),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.A),
|
||||
# Two names concatenated in the QNAME field
|
||||
question(
|
||||
wire(name("AAAAAAAAAAAAAA."), name("AAAAAAAAAAAAAB.")),
|
||||
dns.rdatatype.RdataType.A,
|
||||
),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="twoquestionnames",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=2),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.A),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.NS),
|
||||
),
|
||||
wire(
|
||||
formerr_response_header(qdcount=2),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.A),
|
||||
question(name_pointer(12), dns.rdatatype.RdataType.NS),
|
||||
),
|
||||
id="twoquestiontypes",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=2),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.A),
|
||||
question(name("AAAAAAAAAAAAAA."), dns.rdatatype.RdataType.A),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="dupquestion",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=1, ancount=2),
|
||||
question(root(), dns.rdatatype.RdataType.SOA),
|
||||
soa_rr(minimum=5),
|
||||
soa_rr(minimum=6),
|
||||
),
|
||||
wire(
|
||||
formerr_response_header(qdcount=1),
|
||||
question(root(), dns.rdatatype.RdataType.SOA),
|
||||
),
|
||||
id="dupans",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(ancount=1),
|
||||
rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.MAILB,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
ttl=1,
|
||||
),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="qtypeasanswer",
|
||||
),
|
||||
pytest.param(
|
||||
header(),
|
||||
# This would be NOERROR if it included a COOKIE option,
|
||||
# but is a FORMERR without one.
|
||||
formerr_response_header(),
|
||||
id="noquestions",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(message_id=8, qdcount=1, nscount=1),
|
||||
question(root(), dns.rdatatype.RdataType.A),
|
||||
# Bad NSEC3 owner: X. is not in the base32hex alphabet.
|
||||
nsec3_rr(owner=name("X.")),
|
||||
),
|
||||
wire(
|
||||
formerr_response_header(
|
||||
message_id=8, rcode=dns.rcode.SERVFAIL, qdcount=1
|
||||
),
|
||||
question(root(), dns.rdatatype.RdataType.A),
|
||||
),
|
||||
id="badnsec3owner",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(message_id=9, arcount=1),
|
||||
# Truncated A record (no ttl, length or data)
|
||||
question(root(), dns.rdatatype.RdataType.A),
|
||||
),
|
||||
formerr_response_header(message_id=9),
|
||||
id="shortrecord",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=1),
|
||||
# Truncated question (no class)
|
||||
root(),
|
||||
u16(dns.rdatatype.RdataType.A),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="shortquestion",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=2),
|
||||
question(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.A,
|
||||
dns.rdataclass.RdataClass.IN,
|
||||
),
|
||||
question(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.A,
|
||||
dns.rdataclass.RdataClass(2),
|
||||
),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="questionclass",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=1),
|
||||
a_rr(owner=oversized_name()),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="badrecordname",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=2),
|
||||
a_rr(),
|
||||
rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType(65280),
|
||||
dns.rdataclass.RdataClass(256),
|
||||
ttl=33554433,
|
||||
rdata=a_rdata(),
|
||||
),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="wrongclass",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(qdcount=1, arcount=1),
|
||||
question(root(), dns.rdatatype.RdataType.A),
|
||||
key_rr(rdclass=dns.rdataclass.RdataClass(2)),
|
||||
),
|
||||
wire(
|
||||
formerr_response_header(qdcount=1),
|
||||
question(root(), dns.rdatatype.RdataType.A),
|
||||
),
|
||||
id="keyclass",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=1),
|
||||
# OPT owner should be root
|
||||
opt_rr(owner=name("A.")),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="optwrongname",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=1),
|
||||
malformed_rrsig_rr(),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="malformedrrsig",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(opcode=dns.opcode.UPDATE, nscount=1),
|
||||
rr(
|
||||
root(),
|
||||
dns.rdatatype.RdataType.A,
|
||||
dns.rdataclass.RdataClass.ANY,
|
||||
ttl=0,
|
||||
# Non-empty rdata for DELETE type
|
||||
rdata=b"\x00",
|
||||
),
|
||||
),
|
||||
formerr_response_header(opcode=dns.opcode.UPDATE),
|
||||
id="malformeddeltype",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=1),
|
||||
# Class should be ANY not IN
|
||||
tsig_rr(rdclass=dns.rdataclass.RdataClass.IN),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="tsigwrongclass",
|
||||
),
|
||||
pytest.param(
|
||||
wire(
|
||||
header(arcount=2),
|
||||
tsig_rr(),
|
||||
# TSIG should be the last record
|
||||
a_rr(),
|
||||
),
|
||||
formerr_response_header(),
|
||||
id="tsignotlast",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_formerr(
|
||||
query_wire: bytes,
|
||||
expected_wire: bytes,
|
||||
named_port: int,
|
||||
ns1,
|
||||
) -> None:
|
||||
response_wire = query_raw_tcp(ns1.ip, named_port, query_wire)
|
||||
assert response_wire == expected_wire
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# 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 pytest
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"badnsec3owner.out",
|
||||
"badrecordname.out",
|
||||
"dupans.out",
|
||||
"dupquestion.out",
|
||||
"keyclass.out",
|
||||
"malformeddeltype.out",
|
||||
"malformedrrsig.out",
|
||||
"nametoolong.out",
|
||||
"noquestions.out",
|
||||
"optwrongname.out",
|
||||
"qtypeasanswer.out",
|
||||
"questionclass.out",
|
||||
"shortquestion.out",
|
||||
"shortrecord.out",
|
||||
"tsignotlast.out",
|
||||
"tsigwrongclass.out",
|
||||
"twoquestionnames.out",
|
||||
"twoquestiontypes.out",
|
||||
"wrongclass.out",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_formerr(run_tests_sh):
|
||||
run_tests_sh()
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# SIG not last in additional
|
||||
# header: QUERY additional=2
|
||||
00 00 00 00 00 00 00 00 00 00 00 02
|
||||
# Additional records:
|
||||
# SIG record (class ANY)
|
||||
00 00 fa 00 ff 00 00 00 01 00 12 00 01 02 03 04 05 06 01 02 00 01 00 00 00 00 00 00 00
|
||||
# A record
|
||||
00 00 01 00 01 00 00 00 01 00 04 00 00 00 00
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# TSIG wrong class
|
||||
# header: QUERY, additional=1
|
||||
00 00 00 00 00 00 00 00 00 00 00 01
|
||||
# class should be ANY (00 ff) not IN (00 01)
|
||||
00 00 fa 00 01 00 00 00 01 00 12 00 01 02 03 04 05 06 01 02 00 01 00 00 00 00 00 00 00
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# two questions with different names
|
||||
00 00 00 00 00 02 00 00 00 00 00 00
|
||||
# AAAAAAAAAAAAAA./A/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01
|
||||
00 01
|
||||
# AAAAAAAAAAAAAB./A/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 42 00
|
||||
00 01
|
||||
00 01
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# two questions that differ by type
|
||||
00 00 00 00 00 02 00 00 00 00 00 00
|
||||
# AAAAAAAAAAAAAAA./A/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 01
|
||||
00 01
|
||||
# AAAAAAAAAAAAAAA./NS/IN
|
||||
0e 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00
|
||||
00 02
|
||||
00 01
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# class mismatch
|
||||
# header: QUERY, additional=2
|
||||
00 00 00 00 00 00 00 00 00 00 00 02
|
||||
# ./A/IN
|
||||
00 00 01 00 01 00 00 00 01 00 04 00 00 00 00
|
||||
# ./TYPE65280/CLASS256
|
||||
00 ff 00 01 00 02 00 00 01 00 04 00 00 00 00
|
||||
Loading…
Reference in a new issue