-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[9496] Names SecondaryAuthority traceback #1212
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. I have some minor feedback inline that you can respond to, but please land when you have addressed to your satisfaction. Thanks for fixing it!
| @@ -26,6 +26,9 @@ def getSerial(filename='/tmp/twisted-names.serial'): | |||
| State is stored in the given file. If it does not exist, it is | |||
| created with rw-/---/--- permissions. | |||
|
|
|||
| This manipulates process-global state by calling C{os.umask()}, so it isn't | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this note.
src/twisted/names/authority.py
Outdated
| @@ -60,6 +63,9 @@ class FileAuthority(common.ResolverBase): | |||
| """ | |||
| An Authority that is loaded from a file. | |||
|
|
|||
| This is an abstract class that implements record search logic. To create | |||
| a functional resolver, subclass it and override the L{loadFile()} method. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I don't think the parens are necessary for an epytext reference.)
| Load DNS records from a file. | ||
|
|
||
| This method populates the I{soa} and I{records} attributes. It must be | ||
| overridden in a subclass. It is called once from the initializer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to add a raise NotImplementedError just to make this clearer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered that, but didn't think that would be backward-compatible. Right now a you can subclass FileAuthority, ignore loadFile(), and populate soa and records in your __init__ instead.
Or something even weirder if you like!
twisted/src/twisted/names/test/test_names.py
Lines 38 to 42 in 2d584b9
| class NoFileAuthority(authority.FileAuthority): | |
| def __init__(self, soa, records): | |
| # Yes, skip FileAuthority | |
| common.ResolverBase.__init__(self) | |
| self.soa, self.records = soa, records |
|
|
||
| @param filename: The I{filename} parameter that was passed to the | ||
| initilizer. | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an explicit @return might also be nice.
| This is an implementation detail of L{ResolverBase.getHostByName}. | ||
|
|
||
| @param resolver: The resolver to use for the next query (unless handling | ||
| an I{NS} referral). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I{} or C{} ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I{} is used in most of the places record types are given any syntax, so I stuck with that.
| @returns: L{bytes} suitable for network transmission. | ||
| @rtype: L{bytes} | ||
|
|
||
| @since: Twisted NEXT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 thank you for remembering the compat marker and the right syntax for it :)
| @since: Twisted NEXT | ||
| """ | ||
| if isinstance(domain, unicode): | ||
| domain = domain.encode('idna') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to use twisted/internet/_idna.py here rather than a straight .encode. (The python stdlib codec is a much older version of the spec.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do.
| @@ -0,0 +1 @@ | |||
| twisted.names.secondary.SecondaryAuthority now accepts str for its domain parameter, so twist dns --secondary now functions on Python 3. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this explanation.
| @returns: A L{Deferred} that fires with L{None} when attempted zone | ||
| transfer has completed. | ||
| """ | ||
| if self.transferring: # <-- never true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File a bug for this, too?
| @returns: A L{Deferred} that fires with L{None} when attempted zone | ||
| transfer has completed. | ||
| """ | ||
| if self.transferring: # <-- never true | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it would make the docstring a lie, if it actually worked 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that 😬 summarizes this module too well. :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done what I can for the moment. Filing tickets will have to wait until dornkirk is back up.
| Load DNS records from a file. | ||
|
|
||
| This method populates the I{soa} and I{records} attributes. It must be | ||
| overridden in a subclass. It is called once from the initializer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered that, but didn't think that would be backward-compatible. Right now a you can subclass FileAuthority, ignore loadFile(), and populate soa and records in your __init__ instead.
Or something even weirder if you like!
twisted/src/twisted/names/test/test_names.py
Lines 38 to 42 in 2d584b9
| class NoFileAuthority(authority.FileAuthority): | |
| def __init__(self, soa, records): | |
| # Yes, skip FileAuthority | |
| common.ResolverBase.__init__(self) | |
| self.soa, self.records = soa, records |
| This is an implementation detail of L{ResolverBase.getHostByName}. | ||
|
|
||
| @param resolver: The resolver to use for the next query (unless handling | ||
| an I{NS} referral). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I{} is used in most of the places record types are given any syntax, so I stuck with that.
| @since: Twisted NEXT | ||
| """ | ||
| if isinstance(domain, unicode): | ||
| domain = domain.encode('idna') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do.
| @returns: A L{Deferred} that fires with L{None} when attempted zone | ||
| transfer has completed. | ||
| """ | ||
| if self.transferring: # <-- never true | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that 😬 summarizes this module too well. :(
|
Hmm, so switching to However most of the failures are to do with SRV support: This looks like kjd/idna#17, which indicates that this is a little more complex than it seemed to me at first:
Sadly, it looks like the name handling behavior we want actually varies by record type. We can't apply the I think that I will back out this change and file a ticket once dornkirk returns. |
f4cc847
to
8aa9941
Compare
|
Some quality time with RFC 5890 has convinced me that handling IDNA is entirely inappropriate at this layer. (Indeed, a more technically correct representation of DNS names would be |
|
The build is broken because of hamcrest/PyHamcrest#131 |
|
Now the coverage service is failing. 😠 |
|
@twm I hopefully have addressed the thing that was breaking coverage and kicked the build via the trunk merge, so hopefully you can land when it completes. |
|
Kicking build due to https://twistedmatrix.com/trac/ticket/8879#comment:36 |
|
Thank you @glyph! |
There is a lot of string type confusion in
twisted.names. This is mostly about how domain names (and labels) are represented.I'll be using the Python 3 names for types here:
bytesandstr.The
twisted.internet.interfaces.IResolverinterface defines a bunch of methods, about one per DNS record type. These all take the same arguments, most importantlyname, but the types aren't consistent — thelookupAddress()method definesnameasbytes, but every other method (includinglookupIPV6Address()documents it asstr. Presumably this is a Python 3 porting oversight, but the confusion goes deeper.Since
IResolverhas so many methods all of the implementers in Twisted subclasstwisted.names.common.ResolverBase, which maps all the methods to a_lookup()bottleneck method that can be easily overridden. This includeslookupAddress(), so clearly its type signature is supposed to align with the rest.Internally the query is usually translated into a
dns.Queryinstance, which represents the name as adns.Nameinstance.dns.Namecoerces the name tobytes, encoding Unicode using theidnacodec. So most implementations simply deal with bytes.However, some implementers override a few methods directly. For example,
twisted.names.hosts.Resolveronly deals in IP addresses, so it overrideslookupAddress()andlookupIPV6Address()directly. This produces inconsistent behavior: the hosts implementation only acceptsbytes, notstr.All of this confusion is borne out in the tests: some pass
nameasstr, a few asbytes. There is no systematic coverage of both types.This is basically what's going on in
twisted.names.secondary.SecondaryAuthorityin Twisted #9496: it's not coercing the type of itsdomainparameter tobytes, so it doesn't match the query.The design intent here seems to be that everything is
bytes. However, the reality is that most things support bothbytesandstr, so code relies on that. I tried rejectingstr, but stuff broke (and that wouldn't be backwards compatible anyway. I have therefore:IResolverto permit "bytesandstr" for name parameters. We should not mislead third-party implementers.twisted.names.dns.domainString(), to do the coercion in a consistent way. It's public so third-partyIResolverimplementations can use it.common.BaseResolverwrapper methods, and document_lookup()'s type to be onlybytes. This makes it easier for third-party implementers.IResolverimplementers to also do coercion usingdomainString().dns.Name.nameattribute instead of callingstr()to coercedns.Nameto a string, the former beingbytes.Contributor Checklist:
reviewto the keywords field in Trac, and putting a link to this PR in the comment; it shows up in https://twisted.reviews/ now.