Skip to content
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

Added new static zone type block_a to suppress all A queries for specific zones #819

Merged
merged 1 commit into from
Jan 20, 2023

Conversation

pavel-odintsov
Copy link
Contributor

@pavel-odintsov pavel-odintsov commented Jan 3, 2023

Hello!

I hope you're doing great. May I kindly ask to review my proposal to add new type of static zones?

Let me start from the beginning to explain reasons of adding such capability.

IPv6 deployment is one the the relatively hot trends nowadays and it's finally (after 27 years) getting traction due to clear business demands in many countries.

There are multiple vectors behind and I would like to mention few of them:

  • Prohibitive cost of IPv4 address space ($40+ USD per IP address)
  • Cost of NAT/CGNAT gateways
  • General complexity of IPv4 networks

Considering all these facts I decided to speed up deployment of IPv6 for my home network and my labs and switched it into "IPv6 only mode".

Unfortunately, pretty large number of services still have no IPv6 addresses but it can be easily addressed with NAT64 gateway and custom DNS running DNS64.

So I've deployed NAT64 based on TAYGA and Unbound and most of the services just worked perfectly fine without any issues.

The main blocker level issue I faced with Signal messenger. They have really nice support for IPv6 but due to issues in NodeJS / Electron even if IPv6 / AAAA records exists it tries to contact IPv4 address from A record.

So the only way to fix Signal was to suppress A records completely and keep IPv6 records only. I've created static zone and hardcoded all their IPv6 addresses and it fixed it.

Unfortunately, such approach is not flexible and very unreliable. So I've tried to find way to suppress only A records and keep everything else as-is from their own DNS server.

That's how this implementation was born.

I've mostly copied implementation of transparent-always with very small alterations. I may be wrong in some corner cases and I appreciate careful review for it.

With this patch I have following configuration entries in my Unbound:

local-zone: "chat.signal.org." block_a
local-zone: "cdn.signal.org."  block_a
local-zone: "cdn2.signal.org." block_a
local-zone: "cdsi.signal.org." block_a
local-zone: "storage.signal.org." block_a

In future this capability may be helpful to test that specific domain can run in IPv6 only mode.

As future project I think it may be good idea to add global flag which completely blocks A queries from clients like you have for ANY query.

Thank you!

Copy link
Member

@wcawijngaards wcawijngaards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition code looks nice. This can be added to the code repository.

@wcawijngaards wcawijngaards merged commit 6a4a943 into NLnetLabs:master Jan 20, 2023
wcawijngaards added a commit that referenced this pull request Jan 20, 2023
…hange.

- Merge #819: Added new static zone type block_a to suppress all A
  queries for specific zones.
@wcawijngaards
Copy link
Member

Thank you for the new static zone type! It is added to the code repository, nice if it can help with IPv6 deployment.

@pavel-odintsov
Copy link
Contributor Author

Thank you so much for review. That's great addition to Unbound and it will definitely help to address some issues with IPv6.

@treysis
Copy link

treysis commented Jan 27, 2023

What happens with
local-zone: "does-not-exist.example.org." block_a
if you query that domain for an A record? Will it return NXDOMAIN or NODATA?

@pavel-odintsov
Copy link
Contributor Author

It will be NODATA as only NODATA is safe to return when we have no information about existence of AAAA record:


; <<>> DiG 9.18.8-1-Debian <<>> -t A chat.signal.org @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50254
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;chat.signal.org.		IN	A

;; Query time: 4 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Fri Jan 27 19:46:54 GMT 2023
;; MSG SIZE  rcvd: 44```

@treysis
Copy link

treysis commented Jan 27, 2023

That's a bit problematic. Better would be to just strip it from the answer, not intercept the query. I.e. if the answer returns an IPv4 address, return NODATA instead, if the answer returns NXDOMAIN, return NXDOMAIN. Because if a client simultaneously queries for more than one resource record and the other records return NXDOMAIN, this might be very confusing and breaking DNS specifications.

Simon Kelley of dnsmasq has outlined it a bit more detailed here:
https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2021q3/015523.html

If we have a domain which doesn't exist, say no-such-domain.example.com

The answer to a query for that will be NXDOMAIN, which means that the
domain doesn't exist, and queries for any type of RR will always fail.

A sensible DNS client which sees that will not repeat queries for that
name with other RR types.

With this patch, a query for A no-such-domain.example.com will get a
NODATA response, which implies that there's no A record, but there are
other records, so the client will try for other types. That's
inefficient, but worse, if the client then tries for an AAAA record (for
instance) it will get an NXDOMAIN response. At this point the client is
seeing inconsistent responses from the DNS and all bets are off.

Always returning an NXDOMAIN response is worse, of course, since if that
happens with a domain which does have other RR types, they will
effectively deleted by the wrong NXDOMAIN.

The correct way to do this is to send the query upstream, and then when
the reply comes back, delete any A records from the reply before it's
returned to the original requestor, (and before it's cached.)

@pavel-odintsov
Copy link
Contributor Author

@treysis Thank you for your feedback. I completely agree that my implementation is not ideal. NXDOMAIN is quite dangerous thing in general and for optimisation purposes many DNS providers prefer not to use it and resort to NODATA and I did the same.

The main application of block_a to force application to use IPv6 address instead of IPv4 address. IPv6 addresses in AAAA records may be provided by upstream or may be synthesised using DNS64 and in this case it's very unlikely will cause serious issues as we kind of guarantee that AAAA will be here.

jedisct1 added a commit to jedisct1/unbound that referenced this pull request Feb 15, 2023
* nlnet/master:
  Regenerate configure for the fix acx_nlnetlabs.m4 for -Wstrict-prototypes.
  - Fix acx_nlnetlabs.m4 for -Wstrict-prototypes.
  Fix NLnetLabs#833: [FR] Ability to set the Redis password.
  - Fix NLnetLabs#835: [FR] Ability to use Redis unix sockets.
  - Add NLnetLabs#835: [FR] Ability to use Redis unix sockets.
  Changelog note for NLnetLabs#819, generate configparser.c and comment syntax change. - Merge NLnetLabs#819: Added new static zone type block_a to suppress all A   queries for specific zones.
  - Fix test for new default.
  - Set default for harden-unknown-additional to no. So that it does   not hamper future protocol developments.
  - Add harden-unknown-additional option. Default on and it removes   unknown records from the authority section and additional section.   Thanks to Xiang Li, from NISL Lab, Tsinghua University.
  - Set max-udp-size default to 1232. This is the same default value as   the default value for edns-buffer-size. It restricts client edns   buffer size choices, and makes unbound behave similar to other DNS   resolvers. The new choice, down from 4096 means it is harder to get   large responses from Unbound. Thanks to Xiang Li, from NISL Lab,   Tsinghua University.
  - Fix not following cleared RD flags potentially enables amplification   DDoS attacks, reported by Xiang Li and Wei Xu from NISL Lab,   Tsinghua University. The fix stops query loops, by refusing to send   RD=0 queries to a forwarder, they still get answered from cache.
  Added new static zone type block_a to suppress all A queries for specific zones
@maurice-w
Copy link

Is there a good way to use this globally? local-zone: "." block_a works, but since block_a is based on always_transparent, this results in all local data getting ignored. Creating a transparent local zone for each local data element prevents this, but is a bit of a mess.

@gthess
Copy link
Member

gthess commented Nov 20, 2023

@maurice-w , if you want global configuration you can use the following:

server:
    ...
    module-config: "respip validator iterator" # add your other modules as well but respip is needed
    response-ip: 0.0.0.0/0 redirect

This will still resolve A queries but filter them out to insecure NODATA answers.
The caveat is of course that any filtering and synthesizing on DNSSEC signed zones breaks validation for validating clients.

@maurice-w
Copy link

@gthess Yes, that's how I implemented it for OPNsense. 😉
opnsense/core#5945

I'm currently trying to switch this to the new native block_a implementation to improve performance and reduce the memory footprint.

@maurice-w
Copy link

maurice-w commented Nov 21, 2023

@pavel-odintsov Let me rephrase the question: Would it make sense to modify the implementation of block_a to have it behave more like transparent? This would allow local-data without breaking any use case that I can currently think of.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants