refactor(connlib): use Win32 APIs instead of netsh to set IPs (#8003)

This should be faster and hopefully more reliable.

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
Thomas Eizinger
2025-02-03 06:24:28 +00:00
committed by GitHub
parent 855f89ec89
commit 90fb9b8478

View File

@@ -1,21 +1,23 @@
use crate::windows::{CREATE_NO_WINDOW, TUNNEL_UUID};
use crate::windows::TUNNEL_UUID;
use crate::TUNNEL_NAME;
use anyhow::{Context as _, Result};
use firezone_logging::{anyhow_dyn_err, std_dyn_err};
use ip_network::{IpNetwork, Ipv4Network, Ipv6Network};
use ip_packet::{IpPacket, IpPacketBuf};
use ring::digest;
use std::net::IpAddr;
use std::{
collections::HashSet,
io::{self, Read as _},
net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
os::windows::process::CommandExt,
path::{Path, PathBuf},
process::{Command, Stdio},
sync::Arc,
task::{Context, Poll},
};
use tokio::sync::mpsc;
use windows::Win32::NetworkManagement::IpHelper::{
CreateUnicastIpAddressEntry, InitializeUnicastIpAddressEntry, MIB_UNICASTIPADDRESS_ROW,
};
use windows::Win32::{
NetworkManagement::{
IpHelper::{
@@ -26,6 +28,7 @@ use windows::Win32::{
},
Networking::WinSock::{ADDRESS_FAMILY, AF_INET, AF_INET6},
};
use windows_core::HRESULT;
use wintun::Adapter;
/// The ring buffer size used for Wintun.
@@ -41,7 +44,11 @@ const RING_BUFFER_SIZE: u32 = 0x10_0000;
pub struct TunDeviceManager {
mtu: u32,
/// Interface index of the last created adapter.
iface_idx: Option<u32>,
/// ID of the last created adapter.
luid: Option<wintun::NET_LUID_LH>,
routes: HashSet<IpNetwork>,
}
@@ -51,6 +58,7 @@ impl TunDeviceManager {
pub fn new(mtu: usize, _num_threads: usize) -> Result<Self> {
Ok(Self {
iface_idx: None,
luid: None,
routes: HashSet::default(),
mtu: mtu as u32,
})
@@ -59,40 +67,29 @@ impl TunDeviceManager {
pub fn make_tun(&mut self) -> Result<Tun> {
let tun = Tun::new(self.mtu)?;
self.iface_idx = Some(tun.iface_idx());
self.luid = Some(tun.luid);
Ok(tun)
}
#[tracing::instrument(level = "trace", skip(self))]
pub async fn set_ips(&mut self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Result<()> {
let luid = self
.luid
.context("Cannot set IPs prior to creating an adapter")?;
tracing::debug!("Setting our IPv4 = {}", ipv4);
tracing::debug!("Setting our IPv6 = {}", ipv6);
// TODO: See if there's a good Win32 API for this
// Using netsh directly instead of wintun's `set_network_addresses_tuple` because their code doesn't work for IPv6
Command::new("netsh")
.creation_flags(CREATE_NO_WINDOW)
.arg("interface")
.arg("ipv4")
.arg("set")
.arg("address")
.arg(format!("name=\"{TUNNEL_NAME}\""))
.arg("source=static")
.arg(format!("address={}", ipv4))
.arg("mask=255.255.255.255")
.stdout(Stdio::null())
.status()?;
// SAFETY: Both NET_LUID_LH unions should be the same. We're just copying out
// the u64 value and re-wrapping it, since wintun doesn't refer to the windows
// crate's version of NET_LUID_LH.
let luid = NET_LUID_LH {
Value: unsafe { luid.Value },
};
Command::new("netsh")
.creation_flags(CREATE_NO_WINDOW)
.arg("interface")
.arg("ipv6")
.arg("set")
.arg("address")
.arg(format!("interface=\"{TUNNEL_NAME}\""))
.arg(format!("address={}", ipv6))
.stdout(Stdio::null())
.status()?;
try_set_ip(luid, IpAddr::V4(ipv4)).context("Failed to set IPv4 address")?;
try_set_ip(luid, IpAddr::V6(ipv6)).context("Failed to set IPv6 address")?;
Ok(())
}
@@ -194,6 +191,7 @@ pub struct Tun {
inbound_rx: mpsc::Receiver<IpPacket>,
recv_thread: Option<std::thread::JoinHandle<()>>,
session: Arc<wintun::Session>,
luid: wintun::NET_LUID_LH,
}
impl Drop for Tun {
@@ -234,8 +232,9 @@ impl Tun {
let iface_idx = adapter
.get_adapter_index()
.context("Failed to get adapter index")?;
let luid = adapter.get_luid();
set_iface_config(adapter.get_luid(), mtu).context("Failed to set interface config")?;
set_iface_config(luid, mtu).context("Failed to set interface config")?;
let session = Arc::new(
adapter
@@ -248,6 +247,7 @@ impl Tun {
Ok(Self {
iface_idx,
luid,
recv_thread: Some(recv_thread),
inbound_rx,
session: Arc::clone(&session),
@@ -405,6 +405,45 @@ fn try_set_mtu(luid: NET_LUID_LH, family: ADDRESS_FAMILY, mtu: u32) -> Result<()
Ok(())
}
fn try_set_ip(luid: NET_LUID_LH, ip: IpAddr) -> Result<()> {
const ERROR_OBJECT_ALREADY_EXISTS: HRESULT = HRESULT::from_win32(0x80071392);
// Safety: Docs don't mention anything in regards to safety of this function.
let mut row = unsafe {
let mut row: MIB_UNICASTIPADDRESS_ROW = std::mem::zeroed();
InitializeUnicastIpAddressEntry(&mut row);
row
};
row.InterfaceLuid = luid; // Target our tunnel interface.
row.ValidLifetime = 0xffffffff; // Infinite
match ip {
IpAddr::V4(ipv4) => {
row.Address.si_family = AF_INET;
row.Address.Ipv4 = SocketAddrV4::new(ipv4, 0).into();
row.OnLinkPrefixLength = 32;
}
IpAddr::V6(ipv6) => {
row.Address.si_family = AF_INET6;
row.Address.Ipv6 = SocketAddrV6::new(ipv6, 0, 0, 0).into();
row.OnLinkPrefixLength = 128;
}
}
// Safety: Docs don't mention anything about safety other than having to use `InitializeUnicastIpAddressEntry` and we did that.
match unsafe { CreateUnicastIpAddressEntry(&row) }.ok() {
Ok(()) => {}
Err(e) if e.code() == ERROR_OBJECT_ALREADY_EXISTS => {}
Err(e) => {
return Err(anyhow::Error::new(e).context("Failed to create `UnicastIpAddressEntry`"))
}
}
Ok(())
}
/// Installs the DLL in %LOCALAPPDATA% and returns the DLL's absolute path
///
/// e.g. `C:\Users\User\AppData\Local\dev.firezone.client\data\wintun.dll`