-
Notifications
You must be signed in to change notification settings - Fork 399
feat(connlib): transparently forward non-resources DNS queries #6181
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
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
5989b9e to
87faaf3
Compare
|
@conectado Patch-by-patch review is recommended. I can split some of the first commits out into a dedicated PR if you'd like. |
96329c4 to
f84e329
Compare
f84e329 to
6af88ee
Compare
| #[test] | ||
| fn update_system_dns_works() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_system_dns_without_change_is_a_no_op() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_system_dns_with_change_works() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.0.0.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_to_system_with_sentinels_are_ignored() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ | ||
| ip("1.1.1.1"), | ||
| ip("100.100.111.1"), | ||
| ip("fd00:2021:1111:8000:100:100:111:0"), | ||
| ]); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_wins_over_system() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_with_dns()); | ||
|
|
||
| let dns_changed = client_state.update_dns_mapping(); | ||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
| assert!(!dns_changed); | ||
|
|
||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_change_updates() { | ||
| let mut client_state = ClientState::for_test(); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(InterfaceConfig { | ||
| upstream_dns: vec![dns("8.8.8.8:53")], | ||
| ..interface_config_without_dns() | ||
| }); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("8.8.8.8:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_no_change_is_a_no_op() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_with_dns()); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_sentinels_are_ignored() { | ||
| let mut client_state = ClientState::for_test(); | ||
| let mut config = interface_config_with_dns(); | ||
|
|
||
| let _ = client_state.update_interface_config(config.clone()); | ||
|
|
||
| config.upstream_dns.push(dns("100.100.111.1:53")); | ||
| config | ||
| .upstream_dns | ||
| .push(dns("[fd00:2021:1111:8000:100:100:111:0]:53")); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(config); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn system_dns_takes_over_when_upstream_are_unset() { | ||
| let mut client_state = ClientState::for_test(); | ||
| let _ = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
| let dns_changed = client_state.update_interface_config(interface_config_without_dns()); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.0.0.1:53")]); | ||
| } |
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 decided to remove these tests because this is all covered by tunnel_test.
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.
Does tunnel_test now cover that the sentinel dns should never repeat sentinel ips twice in a row?
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 deliberately left those tests in place! The ones I deleted are only about which DNS servers take effect (system vs upstream).
|
| Report | Wed, August 7, 2024 at 07:07:14 UTC |
| Project | Firezone |
| Branch | refactor/connlib/no-hickory |
| Testbed | github-actions |
Click to view all benchmark results
| Benchmark | Throughput | Throughput Results bits/s | (Δ%) | Throughput Lower Boundary bits/s | (%) |
|---|---|---|---|
| direct-tcp-client2server | ✅ (view plot) | 240,094,492.42 (-0.80%) | 237,403,467.53 (98.88%) |
| direct-tcp-server2client | ✅ (view plot) | 254,570,272.28 (+2.47%) | 241,569,573.08 (94.89%) |
| direct-udp-client2server | ✅ (view plot) | 277,250,928.19 (-4.07%) | 271,846,028.41 (98.05%) |
| direct-udp-server2client | ✅ (view plot) | 400,171,041.24 (+1.01%) | 384,359,562.07 (96.05%) |
| relayed-tcp-client2server | ✅ (view plot) | 250,241,014.12 (+1.75%) | 239,488,547.68 (95.70%) |
| relayed-tcp-server2client | ✅ (view plot) | 260,331,066.20 (+1.10%) | 247,079,397.77 (94.91%) |
| relayed-udp-client2server | ✅ (view plot) | 241,970,245.31 (+5.38%) | 218,548,668.43 (90.32%) |
| relayed-udp-server2client | ✅ (view plot) | 336,763,255.98 (-0.25%) | 318,281,721.70 (94.51%) |
Bencher - Continuous Benchmarking
View Public Perf Page
Docs | Repo | Chat | Help
dd3dd27 to
ddec58c
Compare
|
Reminder to myself to write a changelog entry. |
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.
Would it still be possible to forward queries via DoH using this design? Asking because I know it's going to come up as a requirement at some point (see #4668 )
Yes. It will require us to add a DNS client again or write one ourselves. I do think that byte-for-byte forwarding is the better baseline though. For secure DNS, we'd have to think whether we still want the client to use UDP and we map internally or whether we'd configure the system to directly attempt to do DoH / DoT with us. |
conectado
left a comment
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.
Great work! DNS looks much better now and I expect DNS resolution will be much smoother when we ship this
| #[test] | ||
| fn update_system_dns_works() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_system_dns_without_change_is_a_no_op() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_system_dns_with_change_works() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.0.0.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_to_system_with_sentinels_are_ignored() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_without_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.1.1.1")]); | ||
| let dns_changed = client_state.update_system_resolvers(vec![ | ||
| ip("1.1.1.1"), | ||
| ip("100.100.111.1"), | ||
| ip("fd00:2021:1111:8000:100:100:111:0"), | ||
| ]); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.1.1.1:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_wins_over_system() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_with_dns()); | ||
|
|
||
| let dns_changed = client_state.update_dns_mapping(); | ||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
| assert!(!dns_changed); | ||
|
|
||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_change_updates() { | ||
| let mut client_state = ClientState::for_test(); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(InterfaceConfig { | ||
| upstream_dns: vec![dns("8.8.8.8:53")], | ||
| ..interface_config_without_dns() | ||
| }); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("8.8.8.8:53")]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_no_change_is_a_no_op() { | ||
| let mut client_state = ClientState::for_test(); | ||
| client_state.interface_config = Some(interface_config_with_dns()); | ||
|
|
||
| let dns_changed = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
|
|
||
| assert!(dns_changed); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn upstream_dns_sentinels_are_ignored() { | ||
| let mut client_state = ClientState::for_test(); | ||
| let mut config = interface_config_with_dns(); | ||
|
|
||
| let _ = client_state.update_interface_config(config.clone()); | ||
|
|
||
| config.upstream_dns.push(dns("100.100.111.1:53")); | ||
| config | ||
| .upstream_dns | ||
| .push(dns("[fd00:2021:1111:8000:100:100:111:0]:53")); | ||
|
|
||
| let dns_changed = client_state.update_interface_config(config); | ||
|
|
||
| assert!(!dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), dns_list()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn system_dns_takes_over_when_upstream_are_unset() { | ||
| let mut client_state = ClientState::for_test(); | ||
| let _ = client_state.update_interface_config(interface_config_with_dns()); | ||
|
|
||
| let _ = client_state.update_system_resolvers(vec![ip("1.0.0.1")]); | ||
| let dns_changed = client_state.update_interface_config(interface_config_without_dns()); | ||
|
|
||
| assert!(dns_changed); | ||
| dns_mapping_is_exactly(client_state.dns_mapping(), vec![dns("1.0.0.1:53")]); | ||
| } |
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.
Does tunnel_test now cover that the sentinel dns should never repeat sentinel ips twice in a row?
This will allow us to answer DNS requests from clients that are emitted as `Transmit`s.
ddec58c to
6abd9c6
Compare
|
I dogfooded this for the afternoon on my machine and it worked flawlessly. |
Pull Request is not mergeable
Currently,
connlibdepends onhickory-resolverto perform DNS queries for non-resources. This is unnecessary. Instead of buffering the original UDP DNS query, consulting hickory to resolve the name and mapping the response back, we can simply take the UDP payload and send it via our protected socket directly to the original upstream DNS server.This ensures
connlibis as transparent as possible for DNS queries for non-resources. Additionally, it removes a lot of error handling and other cruft that we currently have to perform because we are using hickory. For example, hickory will automatically retry a DNS query after a certain timeout. However, the OS / client talking toconnlibwill also retry after a certain timeout because it is making DNS queries over an unreliable transport (UDP). It is thus unnecessary for us to do that internally.To correctly test this change, our test-suite needed some refactoring. Specifically, DNS servers are now modelled as dedicated
Hosts that can receive (UDP) traffic.Lastly, we can remove our dependency on
hickory-protoandhickory-resolvereverywhere and only usedomainfor parsing DNS messages.Resolves: #6141.
Related: #6033.
Related: #4800. (Impossible to happen with this design)