Skip to content

Commit 1c6d4a9

Browse files
author
Jacob Halsey
committed
Fix DNS server order to precisely match the way Windows works
1 parent 9b631b8 commit 1c6d4a9

File tree

5 files changed

+60
-186
lines changed

5 files changed

+60
-186
lines changed

Cargo.lock

Lines changed: 1 addition & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
[package]
22
name = "wsl2-dns-agent"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2021"
55
license = "GPL-3.0"
66
description = "An agent that automatically patches your WSL2 DNS configuration for users of Cisco AnyConnect (or similar VPNs)"
77
repository = "https://github.com/jacob-pro/wsl2-dns-agent"
88
homepage = "https://github.com/jacob-pro/wsl2-dns-agent"
99

1010
[dependencies]
11-
backoff = "0.4.0"
1211
chrono = "0.4.19"
1312
configparser = "3.0.0"
1413
dirs = "4.0.0"

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,25 @@ Launch the application.
2727

2828
Note: You must have the `chattr` command installed within your WSL2 distribution.
2929

30+
## Diagnostics
31+
32+
You can view the application log by clicking on the tray icon and "View Log".
33+
34+
Note that this tool *should* apply DNS servers based on their priority in Windows.
35+
36+
For example, from Windows Command Prompt try running:
37+
38+
```cmd
39+
C:\Users\jdhalsey>nslookup.exe google.com
40+
Server: OpenWrt.lan
41+
Address: 10.2.9.254
42+
43+
Non-authoritative answer: ...
44+
```
45+
46+
Therefore `10.2.9.254` will be the first server written to `/etc/resolv.conf`. If the server is not what you expected
47+
then please look at [the DNS guide](./docs/ROUTING.md#step-3---working-windows-dns)
48+
3049
## Advanced options
3150

3251
For advanced use cases you can edit the config file in `%APPDATA%\WSL2 DNS Agent\config.toml`

src/dns.rs

Lines changed: 38 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
use crate::APP_NAME;
2-
use backoff::ExponentialBackoffBuilder;
32
use itertools::Itertools;
4-
use std::fmt::{Display, Formatter};
53
use std::mem::transmute;
64
use std::net::IpAddr;
75
use std::ptr::{null_mut, slice_from_raw_parts};
8-
use std::time::Duration;
96
use thiserror::Error;
107
use win32_utils::net::ToStdSocket;
118
use win32_utils::str::FromWin32Str;
@@ -19,7 +16,6 @@ use windows::Win32::Networking::WinSock::AF_UNSPEC;
1916
#[derive(Debug)]
2017
struct Route {
2118
interface_index: u32,
22-
metric: u32,
2319
destination_prefix_ip: IpAddr,
2420
destination_prefix_len: u8,
2521
}
@@ -31,8 +27,8 @@ impl Route {
3127
}
3228
}
3329

34-
/// Returns list of routes to 0.0.0.0/0 and ::/0
35-
fn get_internet_routes() -> Result<Vec<Route>, Error> {
30+
/// Returns a list of IPv4 and IPv6 routes
31+
fn get_routes() -> Result<Vec<Route>, Error> {
3632
unsafe {
3733
let mut ptr = null_mut::<MIB_IPFORWARD_TABLE2>();
3834
GetIpForwardTable2(AF_UNSPEC.0 as u16, &mut ptr).map_err(Error::GetIpForwardTable2)?;
@@ -46,11 +42,9 @@ fn get_internet_routes() -> Result<Vec<Route>, Error> {
4642
.map(|idx| &table[idx as usize])
4743
.map(|row| Route {
4844
interface_index: row.InterfaceIndex,
49-
metric: row.Metric,
5045
destination_prefix_ip: row.DestinationPrefix.Prefix.to_std_socket_addr().ip(),
5146
destination_prefix_len: row.DestinationPrefix.PrefixLength,
5247
})
53-
.filter(Route::is_internet_route)
5448
.collect::<Vec<_>>();
5549
FreeMibTable(transmute(ptr));
5650
Ok(res)
@@ -131,46 +125,15 @@ fn get_adapters() -> Result<Vec<Adapter>, Error> {
131125
}
132126
}
133127

134-
#[derive(Debug)]
135-
struct RouteAndAdapter<'a> {
136-
route: &'a Route,
137-
adapter: &'a Adapter,
138-
}
139-
140-
impl RouteAndAdapter<'_> {
141-
/// "the overall metric that is used to determine the interface preference is the sum of the
142-
/// route metric and the interface metric"
143-
fn metric_sum(&self) -> u32 {
144-
self.route.metric
145-
+ match self.route.destination_prefix_ip {
146-
IpAddr::V4(_) => self.adapter.ipv4_metric,
147-
IpAddr::V6(_) => self.adapter.ipv6_metric,
148-
}
149-
}
150-
}
151-
152-
impl Display for RouteAndAdapter<'_> {
153-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154-
let m_sum = self.metric_sum();
155-
let s = format!(
156-
"{}/{}, interface index: {}, metric: {} ({} + {}), dns servers: {:?}, dns suffixes: {:?}",
157-
self.route.destination_prefix_ip,
158-
self.route.destination_prefix_len,
159-
self.route.interface_index,
160-
m_sum,
161-
self.route.metric,
162-
m_sum - self.route.metric,
163-
self.adapter.dns_servers,
164-
self.adapter.dns_suffixes
165-
);
166-
f.write_str(s.as_str())
128+
impl Adapter {
129+
// For the purposes of DNS, the interface metric is whichever one is lowest
130+
fn interface_metric(&self) -> u32 {
131+
std::cmp::min(self.ipv4_metric, self.ipv6_metric)
167132
}
168133
}
169134

170135
#[derive(Debug, Error)]
171136
pub enum Error {
172-
#[error("Unable to find adapter for interface: {interface_index}")]
173-
RouteInterfaceMismatch { interface_index: u32 },
174137
#[error("Calls to GetAdaptersAddresses() returned different buffer sizes")]
175138
GetAdaptersAddressesOverflow,
176139
#[error("Call to GetIpForwardTable2() failed: {0}")]
@@ -179,97 +142,47 @@ pub enum Error {
179142
GetAdaptersAddresses(#[source] windows::core::Error),
180143
}
181144

182-
impl Error {
183-
/// Some errors should be retried
184-
pub fn into_backoff(self) -> backoff::Error<Self> {
185-
match &self {
186-
Error::RouteInterfaceMismatch { .. } => self.into(),
187-
Error::GetAdaptersAddressesOverflow { .. } => self.into(),
188-
_ => backoff::Error::Permanent(self),
189-
}
190-
}
191-
}
192-
193-
impl From<backoff::Error<Error>> for Error {
194-
fn from(e: backoff::Error<Error>) -> Self {
195-
match e {
196-
backoff::Error::Permanent(e) => e,
197-
backoff::Error::Transient { err, .. } => err,
198-
}
199-
}
200-
}
201-
202145
#[derive(Debug, Default)]
203146
pub struct DnsConfiguration {
204147
servers: Vec<IpAddr>,
205148
suffixes: Vec<String>,
206149
}
207150

208151
pub fn get_configuration() -> Result<DnsConfiguration, Error> {
209-
let op = || {
210-
{
211-
let routes = get_internet_routes()?;
212-
let adapters = get_adapters()?;
213-
// Match the route interface index with an adapter index
214-
let mut grouped = routes
152+
// List of routes to the internet
153+
let internet_routes = get_routes()?
154+
.into_iter()
155+
.filter(Route::is_internet_route)
156+
.collect::<Vec<_>>();
157+
158+
// DNS priority is determined by interface metric
159+
// However we also want to exclude various system adapters such as WSL
160+
// so we will filter out any adapters that have a route to the internet
161+
let internet_adapters = get_adapters()?
162+
.into_iter()
163+
.filter(|adapter| {
164+
internet_routes
215165
.iter()
216-
.map(|r| {
217-
match r.destination_prefix_ip {
218-
IpAddr::V4(_) => adapters
219-
.iter()
220-
.find(|a| a.ipv4_interface_index.eq(&r.interface_index)),
221-
IpAddr::V6(_) => adapters
222-
.iter()
223-
.find(|a| a.ipv6_interface_index.eq(&r.interface_index)),
224-
}
225-
.ok_or(Error::RouteInterfaceMismatch {
226-
interface_index: r.interface_index,
227-
})
228-
.map(|a| RouteAndAdapter {
229-
route: r,
230-
adapter: a,
231-
})
166+
.any(|route| match route.destination_prefix_ip {
167+
IpAddr::V4(_) => route.interface_index == adapter.ipv4_interface_index,
168+
IpAddr::V6(_) => route.interface_index == adapter.ipv6_interface_index,
232169
})
233-
.collect::<Result<Vec<_>, Error>>()?;
234-
// Sort by the lowest route metrics
235-
grouped.sort_by_key(|r| r.metric_sum());
236-
// Get the best routes for IPv4 and IPv6 internets respectively
237-
let best_v4 = grouped
238-
.iter()
239-
.find(|g| g.route.destination_prefix_ip.is_ipv4());
240-
if let Some(best_v4) = best_v4 {
241-
log::info!("Best IPv4 Route: {}", best_v4);
242-
}
243-
let best_v6 = grouped
244-
.iter()
245-
.find(|g| g.route.destination_prefix_ip.is_ipv6());
246-
if let Some(best_v6) = best_v6 {
247-
log::info!("Best IPv6 Route: {}", best_v6);
248-
}
249-
// Collect the IPv4 and then IPv6 dns configurations
250-
let mut dns_servers = Vec::new();
251-
let mut dns_suffixes = Vec::new();
252-
best_v4.iter().chain(best_v6.iter()).for_each(|g| {
253-
g.adapter.dns_servers.iter().for_each(|d| {
254-
dns_servers.push(d.to_owned());
255-
});
256-
g.adapter.dns_suffixes.iter().for_each(|d| {
257-
dns_suffixes.push(d.to_owned());
258-
});
259-
});
260-
// Ensure servers and suffixes are unique (preserving order)
261-
Ok(DnsConfiguration {
262-
servers: dns_servers.into_iter().unique().collect(),
263-
suffixes: dns_suffixes.into_iter().unique().collect(),
264-
})
265-
}
266-
.map_err(Error::into_backoff)
267-
};
268-
let b = ExponentialBackoffBuilder::new()
269-
.with_initial_interval(Duration::from_millis(50))
270-
.with_max_elapsed_time(Some(Duration::from_secs(1)))
271-
.build();
272-
backoff::retry(b, op).map_err(|e| e.into())
170+
})
171+
.sorted_by_key(Adapter::interface_metric)
172+
.collect::<Vec<_>>();
173+
174+
let servers = internet_adapters
175+
.iter()
176+
.flat_map(|adapter| adapter.dns_servers.clone())
177+
.unique()
178+
.collect::<Vec<_>>();
179+
let suffixes = internet_adapters
180+
.iter()
181+
.flat_map(|adapter| adapter.dns_suffixes.clone())
182+
.unique()
183+
.collect::<Vec<_>>();
184+
185+
Ok(DnsConfiguration { servers, suffixes })
273186
}
274187

275188
impl DnsConfiguration {

src/runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ enum Error {
6969
fn update_dns(config: &Config) -> Result<(), Error> {
7070
let dns = dns::get_configuration()?;
7171
let resolv = dns.generate_resolv();
72-
log::info!("Applying DNS config: {dns:?}");
72+
log::info!("Detected Windows DNS config: {dns:?}");
7373
let wsl = wsl::get_distributions()?
7474
.into_iter()
7575
.filter(|d| d.version == 2)

0 commit comments

Comments
 (0)