Skip to content

Commit b3b79f9

Browse files
committed
Fix allowed-ips routing setup on linux systems
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 5ab60ce commit b3b79f9

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
@@ -163,87 +163,131 @@ pub(crate) fn clear_dns(ifname: &str) -> Result<(), WireguardInterfaceError> {
163163
#[cfg(target_os = "linux")]
164164
const DEFAULT_FWMARK_TABLE: u32 = 51820;
165165

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

174-
let mut unique_allowed_ips = HashSet::new();
175-
let mut default_route = None;
248+
// (ipv4, ipv6)
249+
let mut allowed_ips = (HashSet::new(), HashSet::new());
250+
let mut default_routes = (None, None);
251+
252+
// Gather allowed IPs and default routes
176253
for peer in peers {
177254
for addr in &peer.allowed_ips {
178255
if addr.ip.is_unspecified() {
179-
// Handle default route
180-
default_route = Some(addr);
181-
break;
256+
// Default route - store for later
257+
if addr.ip.is_ipv4() {
258+
default_routes.0 = Some(addr);
259+
} else {
260+
default_routes.1 = Some(addr);
261+
}
262+
continue;
263+
}
264+
// Regular route - add to set
265+
if addr.ip.is_ipv4() {
266+
allowed_ips.0.insert(addr);
267+
} else {
268+
allowed_ips.1.insert(addr);
182269
}
183-
unique_allowed_ips.insert(addr);
184270
}
185271
}
186-
debug!("Allowed IPs that will be used during the peer routing setup: {unique_allowed_ips:?}");
187-
188-
// If there is default route skip adding other routes.
189-
if let Some(default_route) = default_route {
190-
debug!("Found default route in AllowedIPs: {default_route:?}");
191-
let is_ipv6 = default_route.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: {default_route}");
224-
netlink::add_route(ifname, default_route, 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(default_route, 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(default_route, 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");
272+
debug!("Allowed IPs that will be used during the peer routing setup: {allowed_ips:?}");
273+
274+
// Add default route if present, otherwise setup individual allowed IP routes
275+
if let Some(default_route) = default_routes.0 {
276+
setup_default_route(ifname, default_route)?;
277+
} else {
278+
for allowed_ip in allowed_ips.0 {
279+
debug!("Adding a route for allowed IPv4: {allowed_ip}");
280+
netlink::add_route(ifname, allowed_ip, None)?;
281+
debug!("Route added for allowed IPv4: {allowed_ip}");
241282
}
283+
}
284+
if let Some(default_route) = default_routes.1 {
285+
setup_default_route(ifname, default_route)?;
242286
} else {
243-
for allowed_ip in unique_allowed_ips {
244-
debug!("Adding a route for allowed IP: {allowed_ip}");
287+
for allowed_ip in allowed_ips.1 {
288+
debug!("Adding a route for allowed IPv6: {allowed_ip}");
245289
netlink::add_route(ifname, allowed_ip, None)?;
246-
debug!("Route added for allowed IP: {allowed_ip}");
290+
debug!("Route added for allowed IPv6: {allowed_ip}");
247291
}
248292
}
249293
debug!("Peers routing added successfully");

0 commit comments

Comments
 (0)