Skip to content

Commit 812b30f

Browse files
Fix allowed-ips routing setup on linux systems (#90)
Previous implementation skipped adding routes for all individual allowed IPs in the presence of a single unspecified IP address while not distinguishing between IP versions. Therefore IPv6 unspecified IP (::/0) entry in allowed ips prevented all regular IPv4 routes from being added even if unspecified IPv4 address wasn't present in the allowed-ips section.
1 parent 69f1ff7 commit 812b30f

File tree

1 file changed

+109
-65
lines changed

1 file changed

+109
-65
lines changed

src/utils.rs

Lines changed: 109 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -182,87 +182,131 @@ pub(crate) fn clear_dns(ifname: &str) -> Result<(), WireguardInterfaceError> {
182182
#[cfg(target_os = "linux")]
183183
const DEFAULT_FWMARK_TABLE: u32 = 51820;
184184

185-
/// Helper function to add routing.
185+
#[cfg(target_os = "linux")]
186+
fn setup_default_route(
187+
ifname: &str,
188+
addr: &crate::IpAddrMask,
189+
) -> Result<(), WireguardInterfaceError> {
190+
debug!("Found default route in AllowedIPs: {addr:?}");
191+
let is_ipv6 = addr.ip.is_ipv6();
192+
let proto = if is_ipv6 { "-6" } else { "-4" };
193+
debug!("Using the following IP version: {proto}");
194+
195+
debug!("Getting current host configuration for interface {ifname}");
196+
let mut host = netlink::get_host(ifname)?;
197+
debug!("Host configuration read for interface {ifname}");
198+
trace!("Current host: {host:?}");
199+
200+
debug!("Choosing fwmark for marking WireGuard traffic");
201+
let fwmark = match host.fwmark {
202+
Some(fwmark) if fwmark != 0 => fwmark,
203+
Some(_) | None => {
204+
let mut table = DEFAULT_FWMARK_TABLE;
205+
loop {
206+
let output = Command::new("ip")
207+
.args([proto, "route", "show", "table", &table.to_string()])
208+
.output()?;
209+
if output.stdout.is_empty() {
210+
host.fwmark = Some(table);
211+
netlink::set_host(ifname, &host)?;
212+
debug!("Assigned fwmark: {table}");
213+
break;
214+
}
215+
table += 1;
216+
}
217+
table
218+
}
219+
};
220+
debug!("Using the following fwmark for marking WireGuard traffic: {fwmark}");
221+
222+
// Add routes and table rules
223+
debug!("Adding default route: {addr}");
224+
netlink::add_route(ifname, addr, Some(fwmark))?;
225+
debug!("Default route added successfully");
226+
debug!("Adding fwmark rule for the WireGuard interface to prevent routing loops");
227+
netlink::add_fwmark_rule(addr, fwmark)?;
228+
debug!("Fwmark rule added successfully");
229+
230+
debug!("Adding rule for main table to suppress current default gateway");
231+
netlink::add_main_table_rule(addr, 0)?;
232+
debug!("Main table rule added successfully");
233+
234+
if !is_ipv6 {
235+
debug!("Setting net.ipv4.conf.all.src_valid_mark=1");
236+
OpenOptions::new()
237+
.write(true)
238+
.open("/proc/sys/net/ipv4/conf/all/src_valid_mark")?
239+
.write_all(b"1")?;
240+
debug!("net.ipv4.conf.all.src_valid_mark=1 set successfully");
241+
}
242+
Ok(())
243+
}
244+
245+
/// Adds routing entries for allowed IPs of WireGuard peers on a Linux system.
246+
///
247+
/// Iterates over the provided list of peers and installs routing rules based on their
248+
/// allowed IP addresses. It distinguishes between IPv4 and IPv6 addresses, and handles
249+
/// default routes (0.0.0.0/0 or ::/0) separately. If a default route is present, it
250+
/// takes precedence and all specific routes of that IP version are skipped.
251+
///
252+
/// # Arguments
253+
/// * `peers` - A slice of `Peer` objects containing allowed IP configurations.
254+
/// * `ifname` - The name of the WireGuard interface to which routes should be applied.
255+
///
256+
/// # Returns
257+
/// * `Ok(())` on success.
258+
/// * `Err(WireguardInterfaceError)` if any route setup fails.
259+
///
186260
#[cfg(target_os = "linux")]
187261
pub(crate) fn add_peer_routing(
188262
peers: &[Peer],
189263
ifname: &str,
190264
) -> Result<(), WireguardInterfaceError> {
191265
debug!("Adding peer routing for interface: {ifname}");
192266

193-
let mut unique_allowed_ips = HashSet::new();
194-
let mut default_route = None;
267+
// (ipv4, ipv6)
268+
let mut allowed_ips = (HashSet::new(), HashSet::new());
269+
let mut default_routes = (None, None);
270+
271+
// Gather allowed IPs and default routes
195272
for peer in peers {
196273
for addr in &peer.allowed_ips {
197274
if addr.ip.is_unspecified() {
198-
// Handle default route
199-
default_route = Some(addr);
200-
break;
275+
// Default route - store for later
276+
if addr.ip.is_ipv4() {
277+
default_routes.0 = Some(addr);
278+
} else {
279+
default_routes.1 = Some(addr);
280+
}
281+
continue;
282+
}
283+
// Regular route - add to set
284+
if addr.ip.is_ipv4() {
285+
allowed_ips.0.insert(addr);
286+
} else {
287+
allowed_ips.1.insert(addr);
201288
}
202-
unique_allowed_ips.insert(addr);
203289
}
204290
}
205-
debug!("Allowed IPs that will be used during the peer routing setup: {unique_allowed_ips:?}");
206-
207-
// If there is default route skip adding other routes.
208-
if let Some(default_route) = default_route {
209-
debug!("Found default route in AllowedIPs: {default_route:?}");
210-
let is_ipv6 = default_route.ip.is_ipv6();
211-
let proto = if is_ipv6 { "-6" } else { "-4" };
212-
debug!("Using the following IP version: {proto}");
213-
214-
debug!("Getting current host configuration for interface {ifname}");
215-
let mut host = netlink::get_host(ifname)?;
216-
debug!("Host configuration read for interface {ifname}");
217-
trace!("Current host: {host:?}");
218-
219-
debug!("Choosing fwmark for marking WireGuard traffic");
220-
let fwmark = match host.fwmark {
221-
Some(fwmark) if fwmark != 0 => fwmark,
222-
Some(_) | None => {
223-
let mut table = DEFAULT_FWMARK_TABLE;
224-
loop {
225-
let output = Command::new("ip")
226-
.args([proto, "route", "show", "table", &table.to_string()])
227-
.output()?;
228-
if output.stdout.is_empty() {
229-
host.fwmark = Some(table);
230-
netlink::set_host(ifname, &host)?;
231-
debug!("Assigned fwmark: {table}");
232-
break;
233-
}
234-
table += 1;
235-
}
236-
table
237-
}
238-
};
239-
debug!("Using the following fwmark for marking WireGuard traffic: {fwmark}");
240-
241-
// Add routes and table rules
242-
debug!("Adding default route: {default_route}");
243-
netlink::add_route(ifname, default_route, Some(fwmark))?;
244-
debug!("Default route added successfully");
245-
debug!("Adding fwmark rule for the WireGuard interface to prevent routing loops");
246-
netlink::add_fwmark_rule(default_route, fwmark)?;
247-
debug!("Fwmark rule added successfully");
248-
249-
debug!("Adding rule for main table to suppress current default gateway");
250-
netlink::add_main_table_rule(default_route, 0)?;
251-
debug!("Main table rule added successfully");
252-
253-
if !is_ipv6 {
254-
debug!("Setting net.ipv4.conf.all.src_valid_mark=1");
255-
OpenOptions::new()
256-
.write(true)
257-
.open("/proc/sys/net/ipv4/conf/all/src_valid_mark")?
258-
.write_all(b"1")?;
259-
debug!("net.ipv4.conf.all.src_valid_mark=1 set successfully");
291+
debug!("Allowed IPs that will be used during the peer routing setup: {allowed_ips:?}");
292+
293+
// Add default route if present, otherwise setup individual allowed IP routes
294+
if let Some(default_route) = default_routes.0 {
295+
setup_default_route(ifname, default_route)?;
296+
} else {
297+
for allowed_ip in allowed_ips.0 {
298+
debug!("Adding a route for allowed IPv4: {allowed_ip}");
299+
netlink::add_route(ifname, allowed_ip, None)?;
300+
debug!("Route added for allowed IPv4: {allowed_ip}");
260301
}
302+
}
303+
if let Some(default_route) = default_routes.1 {
304+
setup_default_route(ifname, default_route)?;
261305
} else {
262-
for allowed_ip in unique_allowed_ips {
263-
debug!("Adding a route for allowed IP: {allowed_ip}");
306+
for allowed_ip in allowed_ips.1 {
307+
debug!("Adding a route for allowed IPv6: {allowed_ip}");
264308
netlink::add_route(ifname, allowed_ip, None)?;
265-
debug!("Route added for allowed IP: {allowed_ip}");
309+
debug!("Route added for allowed IPv6: {allowed_ip}");
266310
}
267311
}
268312
debug!("Peers routing added successfully");

0 commit comments

Comments
 (0)