Skip to content

Commit

Permalink
Write docs, fix config options
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Halsey committed Jun 6, 2022
1 parent a06d156 commit 1184276
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 57 deletions.
77 changes: 25 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,42 @@
An agent that automatically patches your WSL2 DNS configuration when using Cisco AnyConnect (or similar VPNs that block
split-tunneling).

## Prerequisite #1 - WSL2 Internet Access
## How it works

First you need to ensure your WSL2 distributions can access the internet. Before connecting to the VPN your routes for
WSL2 will look something like (using the `Get-NetAdapter` command in powershell):
1. The agent detects when you connect/disconnect from a VPN.
2. The agent finds the highest priority DNS servers being used by Windows.
3. The agent detects your WSL2 distributions, for each distribution it ensures that `generateResolvConf` is disabled,
and then writes the DNS servers to `/etc/resolv.conf`.

```
ifIndex DestinationPrefix NextHop RouteMetric ifMetric PolicyStore
------- ----------------- ------- ----------- -------- -----------
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```

But when you connect to the VPN, AnyConnect adds a non-functional route with a lower metric:

```
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
56 172.31.64.0/20 10.17.104.1 1 1 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```
## Usage

Unfortunately we cannot remove or modify this route because it will be automatically
[replaced by AnyConnect](https://community.cisco.com/t5/vpn/enforcing-the-split-tunnel-only-access/m-p/4390557/highlight/true#M278089).
However, Windows determines the best route by the lowest sum of interface metric + route metric. What we can do is
increase the AnyConnect interface metric:
**Ensure you have first fixed the route table for WSL2, and not broken the Windows DNS server priority in the process**.
See the [guide](./docs/ROUTING.md) for how to do this.

```powershell
Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000
```
Simply download `wsl2-dns-agent.exe` from the [releases page](https://github.com/jacob-pro/wsl2-dns-agent/releases/latest)

Now the route table will allow WSL2's NAT connection to the Internet, because 5256 is a lower metric than 6001:
Save it to your startup folder (`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`).

```
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
56 172.31.64.0/20 10.17.104.1 1 6000 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```
Launch the application.

(Unfortunately we still cannot connect from Windows to WSL2 via its IP address because AnyConnect blocks this at the
firewall level using Windows Filtering Platform)
## Advanced options

## Prerequisite #2 - Working Windows DNS
For advanced use cases you can edit the config file in `%APPDATA%\WSL2 DNS Agent\config.toml`

The above fix then leads to a problem for the Windows host, when we look at the routes to the internet the AnyConnect
adapter (56) now has a higher metric than Wi-Fi (17) and Ethernet (13):
Example config:

```
56 0.0.0.0/0 10.17.104.1 1 6000 ActiveStore
17 0.0.0.0/0 10.2.9.254 0 50 ActiveStore
13 0.0.0.0/0 10.2.9.254 0 25 ActiveStore
```

This will cause Windows to attempt to connect to the now inaccessible DNS servers on Ethernet and Wi-Fi first, causing
up to a 10-second delay in DNS resolution. The solution is to manually update the network interfaces to have a higher
metric than the AnyConnect interface.
show_notifications = false
Set the Ethernet and Wi-Fi metrics to 6025 and 6050 to ensure they have lower priority than the AnyConnect route (6001)
(Control Panel -> Network and Sharing Center -> Change adapter settings -> Ethernet Properties -> Internet Protocol Version 4 -> Advanced)
# Default options for distributions
[defaults]
apply_dns = true
patch_wsl_conf = true
# If the distribution was previously Stopped, then shutdown once the DNS update is complete
# Note: This option is usually not needed on Windows 11 (because vmIdleTimeout will do it for you)
shutdown = false
```
56 0.0.0.0/0 10.17.104.1 1 6000 ActiveStore
17 0.0.0.0/0 10.2.9.254 0 6050 ActiveStore
13 0.0.0.0/0 10.2.9.254 0 6025 ActiveStore
# Set options for a specific distribution
[distributions.Ubuntu]
apply_dns = false
```
84 changes: 84 additions & 0 deletions docs/ROUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Fixing Route Table for WSL2

## Step #1 - WSL2 Internet Access

First you need to ensure your WSL2 distributions can access the internet. Before connecting to the VPN your routes for
WSL2 will look something like (using the `Get-NetAdapter` command in powershell):

```
ifIndex DestinationPrefix NextHop RouteMetric ifMetric PolicyStore
------- ----------------- ------- ----------- -------- -----------
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```

But when you connect to the VPN, AnyConnect adds a non-functional route with a lower metric:

```
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
56 172.31.64.0/20 10.17.104.1 1 1 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```

Unfortunately we cannot remove or modify this route because it will be automatically
[replaced by AnyConnect](https://community.cisco.com/t5/vpn/enforcing-the-split-tunnel-only-access/m-p/4390557/highlight/true#M278089).
However, Windows determines the best route by the lowest sum of interface metric + route metric. What we can do is
increase the AnyConnect interface metric:

```powershell
Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000
```

Now the route table will allow WSL2's NAT connection to the Internet, because 5256 is a lower metric than 6001:

```
26 172.31.79.255/32 0.0.0.0 256 5000 ActiveStore
26 172.31.64.1/32 0.0.0.0 256 5000 ActiveStore
56 172.31.64.0/20 10.17.104.1 1 6000 ActiveStore
26 172.31.64.0/20 0.0.0.0 256 5000 ActiveStore
```

(Unfortunately we still cannot connect from Windows to WSL2 via its IP address because AnyConnect blocks this at the
firewall level using Windows Filtering Platform)

## Step #2 - Automation

The AnyConnect metric will unfortunately be reset every time the VPN is started, so we need to automate this fix
with task scheduler. Save the above [powershell command](./setCiscoVpnMetric.ps1?raw=true) as `setCiscoVpnMetric.ps1`

Open task scheduler and click "Create task":

- Name: "Update AnyConnect Adapter Interface Metric for WSL2"
- Security options: Check "Run with highest privileges"
- Triggers:
- On an Event, Log: Cisco AnyConnect Secure Mobility Client, Source: acvpnagent, Event ID: 2039
- On an Event, Log: Cisco AnyConnect Secure Mobility Client, Source: acvpnagent, Event ID: 2041
- Actions: Start a program, Program/script: `powershell.exe`,
Add arguments: `-WindowStyle Hidden -NonInteractive -ExecutionPolicy Bypass -File %HOMEPATH%\Documents\setCiscoVpnMetric.ps1`
- Conditions: Uncheck "Start the task only if the computer is on AC power"

## Step #3 - Working Windows DNS

The above fix then leads to a problem for the Windows host, when we look at the routes to the internet the AnyConnect
adapter (56) now has a higher metric than Wi-Fi (17) and Ethernet (13):

```
56 0.0.0.0/0 10.17.104.1 1 6000 ActiveStore
17 0.0.0.0/0 10.2.9.254 0 50 ActiveStore
13 0.0.0.0/0 10.2.9.254 0 25 ActiveStore
```

This will cause Windows to attempt to connect to the now inaccessible DNS servers on Ethernet and Wi-Fi first, causing
up to a 10-second delay in DNS resolution. The solution is to manually update the network interfaces to have a higher
metric than the AnyConnect interface.

Set the Ethernet and Wi-Fi metrics to 6025 and 6050 to ensure they have lower priority than the AnyConnect route (6001)
(Control Panel -> Network and Sharing Center -> Change adapter settings -> Ethernet Properties -> Internet Protocol Version 4 -> Advanced)

```
56 0.0.0.0/0 10.17.104.1 1 6000 ActiveStore
17 0.0.0.0/0 10.2.9.254 0 6050 ActiveStore
13 0.0.0.0/0 10.2.9.254 0 6025 ActiveStore
```
1 change: 1 addition & 0 deletions docs/setCiscoVpnMetric.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Get-NetAdapter | Where-Object {$_.InterfaceDescription -Match "Cisco AnyConnect"} | Set-NetIPInterface -InterfaceMetric 6000
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct DistributionSetting {
/// If the distribution was previously Stopped, then shutdown once the DNS update is complete
/// Note: This option is probably not needed on Windows 11 (because of vmIdleTimeout)
#[serde(default)]
pub restore_state: bool,
pub shutdown: bool,
/// Automatically patch /etc/wsl.conf to disable generateResolvConf
/// Note this will trigger a restart of the distribution
#[serde(default = "r#true")]
Expand Down
11 changes: 7 additions & 4 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ fn update_dns(config: &Config) -> Result<(), Error> {
.collect::<Vec<_>>();
log::info!("Found {} WSL2 distributions", wsl.len());
for d in wsl {
log::info!("Updating DNS for {}", d.name);
if let Err(e) = update_distribution(&d, config.get_distribution_setting(&d.name), &resolv) {
log::error!("Failed to update DNS for {}, due to: {}", d.name, e);
let dist_config = config.get_distribution_setting(&d.name);
if dist_config.apply_dns {
log::info!("Updating DNS for {}", d.name);
if let Err(e) = update_distribution(&d, dist_config, &resolv) {
log::error!("Failed to update DNS for {}, due to: {}", d.name, e);
}
}
}
Ok(())
Expand Down Expand Up @@ -119,7 +122,7 @@ fn update_distribution(
distribution.set_read_only(RESOLV_CONF, true).ok();

// Optionally shutdown the WSL2 distribution once finished
if config.restore_state && distribution.was_stopped() {
if config.shutdown && distribution.was_stopped() {
log::info!("Terminating {}", distribution.name);
distribution.terminate()?;
}
Expand Down

0 comments on commit 1184276

Please sign in to comment.