From 1f28f505ceb304481d12300a8895b598f22e7358 Mon Sep 17 00:00:00 2001 From: XOR-op <17672363+XOR-op@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:48:17 -0700 Subject: [PATCH] feat: icmp proxy --- boltconn/src/app.rs | 1 + boltconn/src/config/config.rs | 1 + boltconn/src/config/inbound.rs | 2 + boltconn/src/network/icmp/mod.rs | 70 +++++++++++++++++++++++++++++ boltconn/src/network/mod.rs | 1 + boltconn/src/network/packet/icmp.rs | 5 +++ boltconn/src/network/tun_device.rs | 46 ++++++++++++++----- 7 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 boltconn/src/network/icmp/mod.rs diff --git a/boltconn/src/app.rs b/boltconn/src/app.rs index a0947d8..ebced7b 100644 --- a/boltconn/src/app.rs +++ b/boltconn/src/app.rs @@ -112,6 +112,7 @@ impl App { tun_udp_tx, udp_tun_rx, false, // TODO: load from configuration + config.inbound.enable_icmp_proxy, ) .map_err(|e| anyhow!("Fail to create TUN: {e}"))?; // create tun device diff --git a/boltconn/src/config/config.rs b/boltconn/src/config/config.rs index 36aebaf..52eb8ff 100644 --- a/boltconn/src/config/config.rs +++ b/boltconn/src/config/config.rs @@ -207,6 +207,7 @@ fn default_speedtest_url() -> String { fn default_inbound_config() -> RawInboundConfig { RawInboundConfig { enable_tun: true, + enable_icmp_proxy: true, http: None, socks5: None, } diff --git a/boltconn/src/config/inbound.rs b/boltconn/src/config/inbound.rs index e60fb55..c413964 100644 --- a/boltconn/src/config/inbound.rs +++ b/boltconn/src/config/inbound.rs @@ -21,6 +21,8 @@ pub enum RawInboundServiceConfig { pub struct RawInboundConfig { #[serde(alias = "enable-tun", default = "default_true")] pub enable_tun: bool, + #[serde(alias = "enable-icmp-proxy", default = "default_true")] + pub enable_icmp_proxy: bool, pub http: Option>, pub socks5: Option>, } diff --git a/boltconn/src/network/icmp/mod.rs b/boltconn/src/network/icmp/mod.rs new file mode 100644 index 0000000..89a8db2 --- /dev/null +++ b/boltconn/src/network/icmp/mod.rs @@ -0,0 +1,70 @@ +use crate::network::packet::icmp::Icmpv4Pkt; +use std::io; +use std::net::{Ipv4Addr, SocketAddr}; +use std::os::fd::AsRawFd; +use tokio::net::UdpSocket; + +pub struct IcmpProxy { + tun_addr: Ipv4Addr, + socket: UdpSocket, + tun_rx: flume::Receiver, +} + +impl IcmpProxy { + pub fn new( + tun_addr: Ipv4Addr, + outbound_iface: &str, + tun_rx: flume::Receiver, + ) -> io::Result { + let socket = socket2::Socket::new( + socket2::Domain::IPV4, + if cfg!(target_os = "linux") { + socket2::Type::RAW + } else { + socket2::Type::DGRAM + }, + Some(socket2::Protocol::ICMPV4), + ) + .map_err(|e| { + tracing::warn!("socket: {e}"); + e + })?; + socket.set_nonblocking(true)?; + #[cfg(not(target_os = "windows"))] + crate::platform::bind_to_device(socket.as_raw_fd(), outbound_iface)?; + #[cfg(target_os = "windows")] + { + use crate::common::io_err; + use crate::platform::get_iface_address; + let IpAddr::V4(local_addr) = get_iface_address(outbound_iface)? else { + return Err(io_err("not ipv4")); + }; + socket.bind(&SocketAddr::new(local_addr.into(), 0).into())?; + } + let socket = UdpSocket::from_std(socket.into())?; + Ok(Self { + tun_addr, + socket, + tun_rx, + }) + } + + pub async fn run(mut self) { + while let Ok(pkt) = self.tun_rx.recv_async().await { + self.handle_request(pkt).await; + } + } + + // drop the packet if anything goes wrong + async fn handle_request(&mut self, icmp_pkt: Icmpv4Pkt) { + if icmp_pkt.is_echo_request() { + let _ = self + .socket + .send_to( + icmp_pkt.ip_pkt().packet_payload(), + SocketAddr::new(icmp_pkt.ip_pkt().dst_addr(), 0), + ) + .await; + } + } +} diff --git a/boltconn/src/network/mod.rs b/boltconn/src/network/mod.rs index 1dc3e12..766570a 100644 --- a/boltconn/src/network/mod.rs +++ b/boltconn/src/network/mod.rs @@ -7,6 +7,7 @@ pub mod tun_device; mod unix_tun; #[cfg(not(target_os = "windows"))] use unix_tun::TunInstance; +mod icmp; #[cfg(target_os = "windows")] mod windows_tun; diff --git a/boltconn/src/network/packet/icmp.rs b/boltconn/src/network/packet/icmp.rs index 1449b6d..cb1b589 100644 --- a/boltconn/src/network/packet/icmp.rs +++ b/boltconn/src/network/packet/icmp.rs @@ -31,6 +31,11 @@ impl Icmpv4Pkt { pkt.msg_type() == Icmpv4Message::EchoRequest } + pub fn is_echo_reply(&self) -> bool { + let pkt = Icmpv4Packet::new_unchecked(self.ip_pkt.packet_payload()); + pkt.msg_type() == Icmpv4Message::EchoReply + } + pub fn set_as_reply(&mut self) { let mut pkt = Icmpv4Packet::new_unchecked(self.ip_pkt.packet_payload_mut()); pkt.set_msg_type(Icmpv4Message::EchoReply); diff --git a/boltconn/src/network/tun_device.rs b/boltconn/src/network/tun_device.rs index 74cb745..61d4189 100644 --- a/boltconn/src/network/tun_device.rs +++ b/boltconn/src/network/tun_device.rs @@ -28,6 +28,7 @@ pub struct TunDevice { udp_tx: flume::Sender, udp_rx: flume::Receiver, ipv6_enabled: bool, + icmp_proxy_enabled: bool, } impl TunDevice { @@ -37,6 +38,7 @@ impl TunDevice { udp_tx: flume::Sender, udp_rx: flume::Receiver, ipv6_enabled: bool, + icmp_proxy_enabled: bool, ) -> io::Result { let (inner, name) = TunInstance::new()?; Ok(TunDevice { @@ -48,6 +50,7 @@ impl TunDevice { udp_tx, udp_rx, ipv6_enabled, + icmp_proxy_enabled, }) } @@ -119,21 +122,38 @@ impl TunDevice { // TunInstance::send_outbound(pkt, self.gw_name.as_str(), self.ipv6_enabled).await // } - pub async fn run(mut self, nat_addr: SocketAddr) -> io::Result<()> { + pub async fn run(mut self, nat_addr: SocketAddr) { let nat_addr = if let SocketAddr::V4(addr) = nat_addr { addr } else { panic!("v6 nat not supported") }; let (mut fd_read, mut fd_write) = split(self.inner.take_fd().unwrap()); + let (mut tun_tx, icmp_rx) = flume::bounded(100); + if self.icmp_proxy_enabled { + match network::icmp::IcmpProxy::new(*nat_addr.ip(), self.gw_name.as_str(), icmp_rx) { + Ok(proxy) => { + tracing::info!("[TUN] ICMP proxy enabled"); + tokio::spawn(proxy.run()); + } + Err(e) => { + tracing::error!("Failed to create ICMP proxy: {:?}", e); + } + }; + } + tracing::info!("[TUN] Running..."); + loop { - // read a ip packet from tun device + // read an ip packet from tun device let handle = BytesMut::with_capacity(MAX_PKT_SIZE); tokio::select! { pkt = Self::recv_ip(&mut fd_read, handle) => { - let pkt = pkt?; - self.forwarding_packet(pkt, &nat_addr, &mut fd_write).await; + let Ok(pkt) = pkt else{ + tracing::error!("Invalid IP packet received from TUN"); + break; + }; + self.forwarding_packet(pkt, &nat_addr, &mut tun_tx, &mut fd_write).await; } data = self.udp_rx.recv_async() => { if let Ok(data) = data{ @@ -160,6 +180,7 @@ impl TunDevice { &self, pkt: IPPkt, nat_addr: &SocketAddrV4, + icmp_tx: &mut flume::Sender, fd_write: &mut WriteHalf, ) { if pkt.src_addr().is_ipv6() { @@ -220,12 +241,17 @@ impl TunDevice { // } } IpProtocol::Icmp => { - // just echo now - let mut pkt = Icmpv4Pkt::new(pkt); - if pkt.is_echo_request() { - pkt.rewrite_addr(dst, src); - pkt.set_as_reply(); - let _ = Self::send_ip(fd_write, pkt.ip_pkt()).await; + if self.icmp_proxy_enabled { + let pkt = Icmpv4Pkt::new(pkt); + let _ = icmp_tx.try_send(pkt); + } else { + // just echo now + let mut pkt = Icmpv4Pkt::new(pkt); + if pkt.is_echo_request() { + pkt.rewrite_addr(dst, src); + pkt.set_as_reply(); + let _ = Self::send_ip(fd_write, pkt.ip_pkt()).await; + } } } _ => {