-
Notifications
You must be signed in to change notification settings - Fork 548
Add support for DNS over HTTPS #393
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
Conversation
|
It would probably be useful to add support for both |
|
@jacobgb24 Ok, I will see what can I do. But do you have any reasons why both |
|
Resolvers must support both per the RFC. I've also never seen an instance of a a resolver supporting one and not the other. The main reason I'd want support for both would be for testing purposes. In that case, it'd be useful to be able to specify the method as a parameter. Not sure how well this fits into the rest of dnspython though, so it may not make sense. One last thing is that the RFC lists a benefit of
|
So, it could be just added as an additional argument to
Yes, but it also says:
Also, does Python's |
|
There are definitely trade-offs to both In terms of caching, it's not limited to the client. There could be caches at any point between the stub and resolver. So regardless of Overall, I'd say |
|
@jacobgb24 I tried to also implement GET support. This is new However, it does not work. It appears that WireQuery is invalid so DNS resolver rejects it. The same query works over POST. This is example URL with invalid WireQuery: Which returns: {
"Status": 2,
"Comment": "DNS packet parse failure (dns: buffer size too small)"
}Because of that, query will not finish. Also, note that I changed |
|
For Here's an example I've used before to give you an idea. import dns.message
import base64
import requests
def create_query(url, record_type="A"):
"""
Creates a DNS query in wire format encoded in base64 for use in GET method
:param url: the url to create a query for e.g. example.com
:param record_type: the desired record type in string format e.g. AAAA
:return: the dns message as a b64 string
"""
message = dns.message.make_query(url, dns.rdatatype.from_text(record_type)).to_wire()
return base64.urlsafe_b64encode(message).decode('utf-8').strip("=")
def decode_b64_answer(data):
"""
Decodes a base64 response into wire format
:param data: the base64 response
:return: a dns wire message
"""
message = dns.message.from_wire(data)
return message
def get_wire(resolver_url, query_name):
"""
Official RFC method. Send a get request to resolver/dns-query with param dns={base64 encoded dns wire query}
:param resolver_url: The resolver to query e.g. 1.1.1.1
:param query_name: The query url e.g. example.com
:return: a dns.message object received from the resolver
"""
headers = {"accept": "application/dns-message"}
payload = {"dns": create_query(query_name)}
url = "https://{}/dns-query".format(resolver_url)
try:
res = requests.get(url, params=payload, headers=headers, stream=True, timeout=10)
return [a.to_text() for a in decode_b64_answer(res.content).answer]
except Exception as e:
return None
print(get_wire("doh-de.blahdns.com", "example.com"))
print(get_wire("cloudflare-dns.com", "github.com"))
print(get_wire("8.8.8.8", "google.com"))Output is: |
|
@jacobgb24 I added support for GET. It can be enabled with the argument |
|
@rthalley Can you merge this? |
|
I will try to look at this one this weekend. |
|
@rthalley Can you now check this? To help you, here is a description how it works:
|
|
I have reviewed this code, and have some feedback:
Not sure if we should go for another revision of this patch, or merge this one and then modify it. I also don't know how to test this at the moment. I'll have to see what open source servers do DoH and how to configure them. |
|
@rthalley Thanks for your reply! I have some questions about your feedback:
How should those parameters be implemented in my method?
Ok, I will rename it.
I think that this should be another PR as this PR already works with HTTPS 1.
Ok, I can do this. |
|
For the parameters: For one_rr_per_rrset and ignore_trailing, look at how dns.query.receive_udp() calls dns.message.from_wire(). Note you should also be passing keyring=query.keyring, and request_mac=query.request_mac (dns.query.receive_udp() gets the parameters from its caller, but its caller, dns.query.udp(), but you can see the caller getting them out of the query. For timeout, you'd want to tell urllib to timeout if requested: timeout, a The source_port and af parameters would be as in the tcp() and tls() commands, and would need to be translated into something relevant for urllib (if it can do it). The source_port parameter is specifying the source port to use for the TCP connection, and the af parameter would be used to say if you wanted only AF_INET or AF_INET6, or if you didn't care. I'm willing to defer these last two to a future time if they're awkward for urllib. I'm OK with handling HTTP 2.0 in another PR. |
|
Just a note on 'af'. We have it for udp() and tcp() for historical reasons, as dnspython didn't used to be clever enough to figure it out. Now it does. The main useful thing I want is the ability to do a v4 connection or a v6 connection, as this is useful when testing nameservers. I suppose you could also argue that you should just have a name with only v4 addresses, and another name with only v6 addresses. But I wouldn't work too hard on this one, as it's not that crucial. |
|
One thought about this is that some public DNS resolvers that support DoH (e.g. Google, Cloudflare) use the same anycast IP for queries over udp, tcp, tls, and https. So instead of worrying about the For example, if you pass in By default it would use the path The issue I see with this is you may not know the IP off the top of your head - you may just know the hostname. So maybe it's not the best approach, but it could be nice for some uses. |
|
@rthalley I added most of the missing parameters. I didn't add @kimbo Although most DoH resolvers use this URL format and same IP, not all of them do this. It would be nice to do this, but it can also be in another PR. It would also be good to support DNS Stamps at some later point (PyPI package for them). |
|
@kimbo - presumably the |
|
@bwelling But how would you handle this from |
|
@filips123 - I think that adding support to the resolver is a separate issue from adding the underlying DoH support, and should be treated as such. |
|
@bwelling Yes but my changes already add support to the resolver. Also, I think that managing different protocols (TCP/UDP/TLS/DoH) could be better acived with something like DNS Stamps which should also be considered in the future. |
Add support for DNS over HTTPS
This adds support for DNS over HTTPS. It adds additional
dns.query.doh()method which usesurllibto get DNS response from DoH resolver. It is used indns.resolver.Resolverwhen a DNS nameserver starts withhttps://(so it is DoH resolver).@rthalley Can you check this? Also, when do you plan to release stable 2.0?