Brian Wellington [Fri, 17 Jul 2020 23:37:53 +0000 (16:37 -0700)]
Changes to blocking model.
Before this change, the synchronous code would check sockets for
readability or writability before doing nonblocking read or write.
This changes them to attempt the read or write first, and then block
if the operation could not complete.
This also removes the no-longer-needed getpeername() call in tcp(),
which was needed to deal with the case where an unconnected socket was
passed in; waiting for writability would block rather than immediately
return an error. By attempting the write first, we get the error
immediately.
Brian Wellington [Fri, 17 Jul 2020 22:46:04 +0000 (15:46 -0700)]
Use the selectors module.
Previously, there was code to either use select.select or select.poll,
depending on OS. This changes it to use the selectors module, using
either SelectSelector or PollSelector, but sharing code otherwise.
In some cases, the caller absolutely doesn't want word breaks. This
shouldn't be the case for any normal DNS record, but is for records that
don't have well-defined text formats, like TSIG and TKEY. Allow them to
pass 0 (or None), to indicate that no word breaks should be added.
Previously, passing either 0 or None resulted in an exception, as the
value was used directly as the step in a slice.
The existing receive_udp() methods are only usable for receiving
responses, as they require an expected destination and check that the
message is from that destination.
This change makes the expected destination (and hence the check)
optional, and returns the address that the message was received from (in
the sync case, this is only done if no destination is provided, for
backwards compatibility).
New tests are added, which required adding generic getsockname() support
to the async backends.
More DNS rcodes are assigned. Support rcode assigned before RFC7873
https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
This also fixes several bugs; the NSEC3 code would properly avoid empty
windows, but the NSEC and CSYNC code did not. Also, none of the wire
parsing routines properly checked to see that the window number was
monotonically increasing.
Bob Halley [Thu, 2 Jul 2020 15:23:52 +0000 (08:23 -0700)]
Rework wire format processing.
Wire format data is now done via a dns.wire.Parser, which does all of the
bookkeeping and also provides convenience routines (e.g. get_uint16() or
get_name()).
If dns.tsigkeyring.from_text() creates dns.tsig.Key objects with the
default algorithm, that causes problems for code that specifies a
different algorithm. There's no good way to handle this, so change
dns.tsigkeyring.from_text() to not create dns.tsig.Key objects unless it
knows the algorithm.
This creates a new class to represent a TSIG key, containing name,
secret, and algorithm.
The keyring format is changed to be {name : key}, and the methods in
dns.tsigkeyring are updated to deal with old and new formats.
The Message class is updated to use dns.tsig.Key, although (to avoid
breaking existing code), it stores them in the keyring field.
Message.use_tsig() can accept either explicit keys, or keyrings; it will
extract and/or create a key.
dns.message.from_wire() can accept either a key or a keyring in the
keyring parameter. If passed a key, it will now raise if the TSIG
record in the message was signed with a different key. If passed a
keyring containing keys (as opposed to bare secrets), it will check that
the TSIG record's algorithm matches that of the key.
Brian Wellington [Tue, 30 Jun 2020 16:27:06 +0000 (09:27 -0700)]
Remove the concept from "first" from TSIG.
The sign() and validate() routines took a "first" parameter, which
indicated that this message was the first in a multi-message sequence.
This isn't needed, as it's identical to "not (ctx and multi)".
Remove the parameter from both, as well as the now-unneeded field in the
message object and message.from_wire() parameter.