Skip to content

Commit

Permalink
feat: icmp proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
XOR-op committed Mar 4, 2025
1 parent 6cf0bfd commit 1f28f50
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 10 deletions.
1 change: 1 addition & 0 deletions boltconn/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions boltconn/src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
2 changes: 2 additions & 0 deletions boltconn/src/config/inbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SingleOrVec<RawInboundServiceConfig>>,
pub socks5: Option<SingleOrVec<RawInboundServiceConfig>>,
}
Expand Down
70 changes: 70 additions & 0 deletions boltconn/src/network/icmp/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Icmpv4Pkt>,
}

impl IcmpProxy {
pub fn new(
tun_addr: Ipv4Addr,
outbound_iface: &str,
tun_rx: flume::Receiver<Icmpv4Pkt>,
) -> io::Result<Self> {
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;
}
}
}
1 change: 1 addition & 0 deletions boltconn/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
5 changes: 5 additions & 0 deletions boltconn/src/network/packet/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 36 additions & 10 deletions boltconn/src/network/tun_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct TunDevice {
udp_tx: flume::Sender<Bytes>,
udp_rx: flume::Receiver<Bytes>,
ipv6_enabled: bool,
icmp_proxy_enabled: bool,
}

impl TunDevice {
Expand All @@ -37,6 +38,7 @@ impl TunDevice {
udp_tx: flume::Sender<Bytes>,
udp_rx: flume::Receiver<Bytes>,
ipv6_enabled: bool,
icmp_proxy_enabled: bool,
) -> io::Result<TunDevice> {
let (inner, name) = TunInstance::new()?;
Ok(TunDevice {
Expand All @@ -48,6 +50,7 @@ impl TunDevice {
udp_tx,
udp_rx,
ipv6_enabled,
icmp_proxy_enabled,
})
}

Expand Down Expand Up @@ -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{
Expand All @@ -160,6 +180,7 @@ impl TunDevice {
&self,
pkt: IPPkt,
nat_addr: &SocketAddrV4,
icmp_tx: &mut flume::Sender<Icmpv4Pkt>,
fd_write: &mut WriteHalf<T>,
) {
if pkt.src_addr().is_ipv6() {
Expand Down Expand Up @@ -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;
}
}
}
_ => {
Expand Down

0 comments on commit 1f28f50

Please sign in to comment.