Add fake tcp tunnel (experimental) (#1673)

support faketcp to avoid tcp-over-tcp problem.
linux/macos/windows are supported.

better to be used in internet env, the maximum 
performance is majorly limited by windivert/raw socket.
This commit is contained in:
KKRainbow
2025-12-25 00:10:32 +08:00
committed by GitHub
parent 0712ef762d
commit 28cd6da502
27 changed files with 3744 additions and 19 deletions
+482
View File
@@ -0,0 +1,482 @@
mod netfilter;
mod packet;
mod stack;
use std::net::{IpAddr, Ipv4Addr, UdpSocket};
use std::sync::Arc;
use std::{net::SocketAddr, pin::Pin};
use bytes::BytesMut;
use pnet::datalink;
use pnet::util::MacAddr;
use tokio::io::AsyncReadExt;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use crate::common::scoped_task::ScopedTask;
use crate::tunnel::fake_tcp::netfilter::create_tun;
use crate::tunnel::{common::TunnelWrapper, Tunnel, TunnelError, TunnelInfo, TunnelListener};
use futures::Future;
use dashmap::DashMap;
struct IpToIfNameCache {
ip_to_ifname: DashMap<IpAddr, (String, Option<MacAddr>)>,
}
impl IpToIfNameCache {
fn new() -> Self {
Self {
ip_to_ifname: DashMap::new(),
}
}
fn reload_ip_to_ifname(&self) {
self.ip_to_ifname.clear();
let interfaces = datalink::interfaces();
for iface in interfaces {
for ip in iface.ips.iter() {
self.ip_to_ifname
.insert(ip.ip(), (iface.name.clone(), iface.mac));
}
}
}
fn get_ifname(&self, ip: &IpAddr) -> Option<(String, Option<MacAddr>)> {
if let Some(ifname) = self.ip_to_ifname.get(ip) {
Some(ifname.clone())
} else {
self.reload_ip_to_ifname();
self.ip_to_ifname.get(ip).map(|s| s.clone())
}
}
}
fn get_faketcp_tunnel_type_str(driver_type: &str) -> String {
format!("faketcp_{}", driver_type)
}
pub struct FakeTcpTunnelListener {
addr: url::Url,
os_listener: Option<tokio::net::TcpListener>,
// interface_name -> fake tcp stack
stack_map: DashMap<String, Arc<Mutex<stack::Stack>>>,
// a cache from ip addr to interface name
ip_to_ifname: IpToIfNameCache,
}
impl FakeTcpTunnelListener {
pub fn new(addr: url::Url) -> Self {
// Define filter: Capture all packets (or refine this if needed)
// For FakeTCP, we probably want to capture packets destined to us?
// But `stack::Stack` handles IP/TCP logic.
// Maybe we just capture everything for now as a raw tunnel?
// Or better, filter based on some criteria?
// The user said "satisfy filter function".
// Let's create a filter that accepts everything for now, or maybe only IP packets?
FakeTcpTunnelListener {
addr,
os_listener: None,
stack_map: DashMap::new(),
ip_to_ifname: IpToIfNameCache::new(),
}
}
async fn do_accept(&mut self) -> Result<AcceptResult, TunnelError> {
loop {
match self.os_listener.as_mut().unwrap().accept().await {
Ok((s, remote_addr)) => {
let Ok(local_addr) = s.local_addr() else {
tracing::warn!("accept fail with local_addr error");
continue;
};
let Some((interface_name, mac)) =
self.ip_to_ifname.get_ifname(&local_addr.ip())
else {
tracing::warn!("accept fail with interface_name error");
continue;
};
return Ok(AcceptResult {
socket: s,
local_addr,
remote_addr,
interface_name,
mac,
});
}
Err(e) => {
use std::io::ErrorKind::*;
if matches!(
e.kind(),
NotConnected | ConnectionAborted | ConnectionRefused | ConnectionReset
) {
tracing::warn!(?e, "accept fail with retryable error: {:?}", e);
continue;
}
tracing::warn!(?e, "accept fail");
return Err(e.into());
}
}
}
}
async fn get_stack(
&self,
accept_result: &AcceptResult,
) -> Result<Arc<Mutex<stack::Stack>>, TunnelError> {
let local_socket_addr = accept_result.local_addr;
let interface_name = &accept_result.interface_name;
let (local_ip, local_ip6) = match local_socket_addr.ip() {
IpAddr::V4(ip) => (Some(ip), None),
IpAddr::V6(ip) => (None, Some(ip)),
};
let ret = self
.stack_map
.entry(interface_name.to_string())
.or_insert_with(|| {
let tun = create_tun(interface_name, None, local_socket_addr);
tracing::info!(
?local_socket_addr,
"create new stack with interface_name: {:?}",
interface_name
);
// TODO: Get local MAC address of the interface
Arc::new(Mutex::new(stack::Stack::new(
tun,
local_ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
local_ip6,
accept_result.mac,
)))
})
.clone();
Ok(ret)
}
}
fn build_os_socket_reader_task(mut socket: TcpStream) -> ScopedTask<()> {
let os_socket_reader_task: ScopedTask<()> = tokio::spawn(async move {
// read the os socket until it's closed
let mut buf = [0u8; 1024];
while let Ok(size) = socket.read(&mut buf).await {
tracing::trace!("read {} bytes from os socket", size);
if size == 0 {
break;
}
}
tracing::info!("FakeTcpTunnelListener os socket closed");
})
.into();
os_socket_reader_task
}
#[derive(Debug)]
struct AcceptResult {
socket: TcpStream,
local_addr: SocketAddr,
remote_addr: SocketAddr,
interface_name: String,
mac: Option<MacAddr>,
}
#[async_trait::async_trait]
impl TunnelListener for FakeTcpTunnelListener {
async fn listen(&mut self) -> Result<(), TunnelError> {
let port = self.addr.port().unwrap_or(0);
let bind_addr = crate::tunnel::check_scheme_and_get_socket_addr::<SocketAddr>(
&self.addr,
"faketcp",
crate::tunnel::IpVersion::Both,
)
.await?;
let os_listener = tokio::net::TcpListener::bind(bind_addr).await?;
tracing::info!(port, "FakeTcpTunnelListener listening");
self.os_listener = Some(os_listener);
// self.stack.lock().await.listen(port);
Ok(())
}
async fn accept(&mut self) -> Result<Box<dyn Tunnel>, TunnelError> {
tracing::debug!("FakeTcpTunnelListener waiting for accept");
let res = self.do_accept().await?;
let stack = self.get_stack(&res).await?;
let socket = stack
.lock()
.await
.alloc_established_socket(res.local_addr, res.remote_addr, stack::State::Established)
.await;
tracing::info!(
?res,
remote = socket.remote_addr().to_string(),
"FakeTcpTunnelListener accepted connection"
);
let info = TunnelInfo {
tunnel_type: get_faketcp_tunnel_type_str(stack.lock().await.driver_type()),
local_addr: Some(self.local_url().into()),
remote_addr: Some(
crate::tunnel::build_url_from_socket_addr(
&socket.remote_addr().to_string(),
"faketcp",
)
.into(),
),
};
// We treat the fake tcp socket as a datagram tunnel directly
// The reader/writer will interface with the socket using recv_bytes/send
// We need to adapt the socket to ZCPacketStream and ZCPacketSink
// Since FakeTcpTunnel is a datagram tunnel, we don't need FramedReader/Writer (which are for stream based tunnels like TCP)
// We should wrap the socket into something that produces/consumes ZCPacket directly.
let socket = Arc::new(socket);
let reader = FakeTcpStream::new(socket.clone());
let writer = FakeTcpSink::new(socket);
Ok(Box::new(TunnelWrapper::new_with_associate_data(
reader,
writer,
Some(info),
Some(Box::new(build_os_socket_reader_task(res.socket))),
)))
}
fn local_url(&self) -> url::Url {
self.addr.clone()
}
}
pub struct FakeTcpTunnelConnector {
addr: url::Url,
ip_to_if_name: IpToIfNameCache,
}
impl FakeTcpTunnelConnector {
pub fn new(addr: url::Url) -> Self {
FakeTcpTunnelConnector {
addr,
ip_to_if_name: IpToIfNameCache::new(),
}
}
}
fn get_local_ip_for_destination(destination: IpAddr) -> Option<IpAddr> {
// 使用一个不可路由的、私有的、或回环地址创建一个临时的 socket,让内核自动选择源接口。
// 对于 IPv4,使用 0.0.0.0; 对于 IPv6,使用 ::
let bind_addr = if destination.is_ipv4() {
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
} else {
IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))
};
// 绑定到一个临时端口 (0)
let socket = UdpSocket::bind((bind_addr, 0)).ok()?;
// 尝试连接到目标地址。这不会真正发送数据包,只是让内核确定路由。
socket.connect((destination, 80)).ok()?; // 使用一个常见的端口,例如 80
// 获取 socket 的本地地址信息
socket.local_addr().map(|addr| addr.ip()).ok()
}
#[async_trait::async_trait]
impl crate::tunnel::TunnelConnector for FakeTcpTunnelConnector {
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError> {
let remote_addr = crate::tunnel::check_scheme_and_get_socket_addr::<SocketAddr>(
&self.addr,
"faketcp",
crate::tunnel::IpVersion::Both,
)
.await?;
let local_ip = get_local_ip_for_destination(remote_addr.ip())
.ok_or(TunnelError::InternalError("Failed to get local ip".into()))?;
let os_socket = tokio::net::TcpSocket::new_v4()?;
os_socket.bind("0.0.0.0:0".parse().unwrap())?;
let local_port = os_socket.local_addr()?.port();
let local_addr = SocketAddr::new(local_ip, local_port);
let (interface_name, mac) =
self.ip_to_if_name
.get_ifname(&local_ip)
.ok_or(TunnelError::InternalError(
"Failed to get interface name".into(),
))?;
let (local_ip, local_ip6) = match local_ip {
IpAddr::V4(ip) => (Some(ip), None),
IpAddr::V6(ip) => (None, Some(ip)),
};
let tun = create_tun(&interface_name, Some(remote_addr), local_addr);
let local_ip = local_ip.unwrap_or("0.0.0.0".parse().unwrap());
let mut stack = stack::Stack::new(tun, local_ip, local_ip6, mac);
let driver_type = stack.driver_type();
let socket = stack
.alloc_established_socket(local_addr, remote_addr, stack::State::SynSent)
.await;
let os_stream = os_socket.connect(remote_addr).await?;
tracing::info!(?remote_addr, "FakeTcpTunnelConnector connecting");
socket.recv_bytes().await.ok_or(TunnelError::InternalError(
"Failed to recv bytes to establish connection".into(),
))?;
tracing::info!(local_addr = ?socket.local_addr(), "FakeTcpTunnelConnector connected");
let info = TunnelInfo {
tunnel_type: get_faketcp_tunnel_type_str(driver_type),
local_addr: Some(
crate::tunnel::build_url_from_socket_addr(
&socket.local_addr().to_string(),
"faketcp",
)
.into(),
),
remote_addr: Some(self.addr.clone().into()),
};
let socket = Arc::new(socket);
let reader = FakeTcpStream::new(socket.clone());
let writer = FakeTcpSink::new(socket);
Ok(Box::new(TunnelWrapper::new_with_associate_data(
reader,
writer,
Some(info),
Some(Box::new((build_os_socket_reader_task(os_stream), stack))),
)))
}
fn remote_url(&self) -> url::Url {
self.addr.clone()
}
}
use crate::tunnel::packet_def::{ZCPacket, ZCPacketType};
use crate::tunnel::{SinkError, SinkItem, StreamItem};
use futures::{Sink, Stream};
use std::task::{Context as TaskContext, Poll};
struct FakeTcpStream {
socket: Arc<stack::Socket>,
#[allow(clippy::type_complexity)]
recv_fut: Option<Pin<Box<dyn Future<Output = Option<Vec<u8>>> + Send + Sync>>>,
}
impl FakeTcpStream {
fn new(socket: Arc<stack::Socket>) -> Self {
Self {
socket,
recv_fut: None,
}
}
}
impl Stream for FakeTcpStream {
type Item = StreamItem;
fn poll_next(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
let s = self.get_mut();
if s.recv_fut.is_none() {
let socket = s.socket.clone();
s.recv_fut = Some(Box::pin(async move { socket.recv_bytes().await }));
}
match s.recv_fut.as_mut().unwrap().as_mut().poll(cx) {
Poll::Ready(Some(data)) => {
let mut buf = BytesMut::new();
buf.extend_from_slice(&data);
let packet = ZCPacket::new_from_buf(buf, ZCPacketType::DummyTunnel);
s.recv_fut = None;
Poll::Ready(Some(Ok(packet)))
}
Poll::Ready(None) => {
// 连接关闭
s.recv_fut = None;
Poll::Ready(None)
}
Poll::Pending => Poll::Pending,
}
}
}
struct FakeTcpSink {
socket: Arc<stack::Socket>,
}
impl FakeTcpSink {
fn new(socket: Arc<stack::Socket>) -> Self {
Self { socket }
}
}
impl Sink<SinkItem> for FakeTcpSink {
type Error = SinkError;
fn poll_ready(
self: Pin<&mut Self>,
_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: SinkItem) -> Result<(), Self::Error> {
// We need to send the packet as bytes
// The item is ZCPacket, which has into_bytes() method
let bytes = item.convert_type(ZCPacketType::DummyTunnel).into_bytes();
// Let's just spawn for now as a simple implementation, noting the limitation.
self.socket.try_send(&bytes);
Ok(())
}
fn poll_flush(
self: Pin<&mut Self>,
_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(
self: Pin<&mut Self>,
_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
self.socket.close();
Poll::Ready(Ok(()))
}
}
#[cfg(test)]
mod tests {
use crate::tunnel::common::tests::_tunnel_pingpong;
use super::*;
#[tokio::test]
async fn faketcp_pingpong() {
#[cfg(target_family = "unix")]
{
if unsafe { nix::libc::geteuid() } != 0 {
return;
}
}
let listener = FakeTcpTunnelListener::new("faketcp://0.0.0.0:31011".parse().unwrap());
let connector = FakeTcpTunnelConnector::new("faketcp://127.0.0.1:31011".parse().unwrap());
_tunnel_pingpong(listener, connector).await
}
}