Skip to content

Commit d5506d1

Browse files
committed
feat(client): support linux masq and snat for local ciders
1 parent 2b5a3f5 commit d5506d1

File tree

5 files changed

+340
-10
lines changed

5 files changed

+340
-10
lines changed

src/client/main.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clap::Parser;
2-
use std::sync::Arc;
2+
use std::sync::{Arc};
33
use std::time::Duration;
44
use tokio::time::interval;
55
use crate::client::{Args, P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT};
@@ -73,8 +73,25 @@ pub async fn run_client() {
7373
None
7474
};
7575

76+
// Check iptables availability if masq is enabled (Linux only)
77+
#[cfg(target_os = "linux")]
78+
{
79+
if args.masq {
80+
if let Err(e) = crate::utils::sys_route::SysRoute::check_iptables_available() {
81+
eprintln!("❌ Error: {}", e);
82+
eprintln!("\nPlease install iptables or run without --masq option.");
83+
std::process::exit(1);
84+
}
85+
}
86+
}
87+
7688
// initialize TUN device
77-
let mut dev = match init_device(&device_config).await {
89+
#[cfg(target_os = "linux")]
90+
let enable_masq = args.masq;
91+
#[cfg(not(target_os = "linux"))]
92+
let enable_masq = false;
93+
94+
let mut dev = match init_device(&device_config, enable_masq).await {
7895
Ok(d) => d,
7996
Err(e) => {
8097
tracing::error!("Failed to initialize device: {}", e);
@@ -95,10 +112,10 @@ pub async fn run_client() {
95112
run_event_loop(&mut relay_handler, &mut p2p_handler, &mut dev).await;
96113
}
97114

98-
async fn init_device(device_config: &HandshakeReplyFrame) -> crate::Result<DeviceHandler> {
115+
async fn init_device(device_config: &HandshakeReplyFrame, enable_masq: bool) -> crate::Result<DeviceHandler> {
99116
tracing::info!("Initializing device with config: {:?}", device_config);
100117
let mut dev = DeviceHandler::new();
101-
let tun_index = dev.run(device_config).await?;
118+
let tun_index = dev.run(device_config, enable_masq).await?;
102119

103120
// Log TUN index (Windows only)
104121
if let Some(idx) = tun_index {

src/client/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,10 @@ pub struct Args {
4545
/// HTTP status server port (disabled if not specified)
4646
#[arg(long)]
4747
pub http_port: Option<u16>,
48+
49+
/// Enable MASQUERADE (NAT) for VPN traffic (Linux only)
50+
/// This enables iptables MASQUERADE rule to allow VPN clients to access external networks
51+
#[cfg(target_os = "linux")]
52+
#[arg(long)]
53+
pub masq: bool,
4854
}

src/crypto/aes256.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,18 @@ impl Block for Aes256Block {
123123
Ok(())
124124
}
125125
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::*;
130+
#[test]
131+
fn test_aes_256_gcm() {
132+
let block = Aes256Block::from_string("rustun");
133+
let msg = String::from("hello");
134+
let mut msg_bytes = msg.as_bytes().to_vec();
135+
block.encrypt(&mut msg_bytes).unwrap();
136+
println!("msg_bytes = {:?}", msg_bytes);
137+
block.decrypt(&mut msg_bytes).unwrap();
138+
println!("msg_bytes = {:?}", msg_bytes);
139+
}
140+
}

src/utils/device.rs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use tun::AbstractDevice;
55
use crate::codec::frame::{HandshakeReplyFrame, PeerDetail};
66
use crate::utils::sys_route::SysRoute;
77
use std::collections::HashSet;
8+
#[allow(unused_imports)]
9+
use crate::utils::sys_route::{ip_to_network, mask_to_prefix_length};
810

911
const DEFAULT_MTU: u16 = 1430;
1012

@@ -41,7 +43,7 @@ impl Device {
4143
}
4244
}
4345

44-
pub async fn run(&mut self, ready: oneshot::Sender<Option<i32>>) -> crate::Result<()> {
46+
pub async fn run(&mut self, ready: oneshot::Sender<Option<i32>>, name: oneshot::Sender<Option<String>>) -> crate::Result<()> {
4547
let mut config = tun::Configuration::default();
4648
config
4749
.address(self.ip.clone())
@@ -69,6 +71,16 @@ impl Device {
6971
#[cfg(not(target_os = "windows"))]
7072
let tun_index: Option<i32> = None;
7173

74+
// Get interface name (Linux only, for MASQUERADE)
75+
#[cfg(target_os = "linux")]
76+
{
77+
let interface_name = dev.tun_name().ok();
78+
let _ = name.send(interface_name);
79+
}
80+
81+
#[cfg(not(target_os = "linux"))]
82+
let _ = name.send(None);
83+
7284
let _ = ready.send(tun_index);
7385
let mut buf = vec![0; 2048];
7486
loop {
@@ -102,7 +114,10 @@ impl Device {
102114
pub struct DeviceHandler {
103115
peer_details: Vec<PeerDetail>,
104116
private_ip: String,
117+
mask: String,
118+
local_ciders: Vec<String>,
105119
tun_index: Option<i32>,
120+
interface_name: Option<String>,
106121
inbound_rx: Option<mpsc::Receiver<Vec<u8>>>,
107122
outbound_tx: Option<mpsc::Sender<Vec<u8>>>,
108123
pub rx_bytes: usize,
@@ -114,28 +129,34 @@ impl DeviceHandler {
114129
Self {
115130
peer_details: vec![],
116131
private_ip: String::new(),
132+
mask: String::new(),
133+
local_ciders: vec![],
117134
tun_index: None,
135+
interface_name: None,
118136
inbound_rx: None,
119137
outbound_tx: None,
120138
rx_bytes: 0,
121139
tx_bytes: 0,
122140
}
123141
}
124142

125-
pub async fn run(&mut self, cfg: &HandshakeReplyFrame) -> crate::Result<Option<i32>> {
143+
pub async fn run(&mut self, cfg: &HandshakeReplyFrame, enable_masq: bool) -> crate::Result<Option<i32>> {
126144
let (inbound_tx, inbound_rx) = mpsc::channel(1000);
127145
let (outbound_tx, outbound_rx) = mpsc::channel(1000);
128146
self.inbound_rx = Some(inbound_rx);
129147
self.outbound_tx = Some(outbound_tx);
130148
self.private_ip = cfg.private_ip.clone();
149+
self.mask = cfg.mask.clone();
150+
self.local_ciders = cfg.ciders.clone();
131151

132152
let mut dev = Device::new(cfg.private_ip.clone(),
133153
cfg.mask.clone(),
134154
DEFAULT_MTU,
135155
inbound_tx, outbound_rx);
136156
let (ready_tx, ready_rx) = oneshot::channel();
157+
let (name_tx, name_rx) = oneshot::channel();
137158
tokio::spawn(async move {
138-
let res = dev.run(ready_tx).await;
159+
let res = dev.run(ready_tx, name_tx).await;
139160
match res {
140161
Ok(_) => (),
141162
Err(e) => tracing::error!("device handler fail: {:?}", e),
@@ -144,6 +165,21 @@ impl DeviceHandler {
144165

145166
let tun_index = ready_rx.await.unwrap_or(None);
146167
self.tun_index = tun_index;
168+
169+
if let Ok(Some(name)) = name_rx.await {
170+
self.interface_name = Some(name);
171+
}
172+
173+
174+
if enable_masq {
175+
if let Err(e) = self.enable_masquerade() {
176+
tracing::error!("Failed to enable MASQUERADE: {:?}", e);
177+
}
178+
if let Err(e) = self.enable_snat() {
179+
tracing::warn!("Failed to enable SNAT: {:?}", e);
180+
}
181+
}
182+
147183
Ok(tun_index)
148184
}
149185

@@ -227,9 +263,58 @@ impl DeviceHandler {
227263

228264
// Update stored routes
229265
self.peer_details = new_routes;
230-
266+
231267
tracing::info!("Route reload complete");
232268
}
269+
270+
/// Enable MASQUERADE (NAT) for VPN interface (Linux only)
271+
/// Uses source network address instead of interface name for better reliability
272+
pub fn enable_masquerade(&mut self) -> crate::Result<()> {
273+
let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?;
274+
275+
let sys_route = SysRoute::new();
276+
sys_route.enable_masquerade_by_source(&cidr)?;
277+
Ok(())
278+
}
279+
280+
/// Disable MASQUERADE (NAT) for VPN interface (Linux only)
281+
pub fn disable_masquerade(&mut self) -> crate::Result<()> {
282+
let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?;
283+
284+
let sys_route = SysRoute::new();
285+
sys_route.disable_masquerade_by_source(&cidr)?;
286+
Ok(())
287+
}
288+
289+
/// Convert IP address and subnet mask to CIDR notation
290+
fn ip_mask_to_cidr(&self, ip: &str, mask: &str) -> crate::Result<String> {
291+
// Parse subnet mask to prefix length
292+
let prefix_len = mask_to_prefix_length(mask)?;
293+
let network = ip_to_network(ip, mask)?;
294+
Ok(format!("{}/{}", network, prefix_len))
295+
}
296+
297+
/// Enable SNAT for local network segments to use virtual IP (Linux only)
298+
/// This makes packets from local ciders appear as coming from virtual IP
299+
pub fn enable_snat(&mut self) -> crate::Result<()> {
300+
let sys_route = SysRoute::new();
301+
302+
for cidr in &self.local_ciders {
303+
sys_route.enable_snat_for_local_network(cidr, "", &self.private_ip)?;
304+
tracing::info!("Enabled SNAT for local network {} -> {}", cidr, self.private_ip);
305+
}
306+
Ok(())
307+
}
308+
309+
/// Disable SNAT for local network segments (Linux only)
310+
pub fn disable_snat(&mut self) -> crate::Result<()> {
311+
let sys_route = SysRoute::new();
312+
313+
for cidr in &self.local_ciders {
314+
sys_route.disable_snat_for_local_network(cidr, "", &self.private_ip)?;
315+
}
316+
Ok(())
317+
}
233318
}
234319

235320
impl Default for DeviceHandler {

0 commit comments

Comments
 (0)