前言

众所周知,wireguard工作在IP层,直接转发IP数据包。网络上的wireguard部署教程通常基于iptables的nat功能,但这样部署的服务器在网络质量较差时无法达到较快的TCP连接速度:因为直接转发IP数据包的工作模式下,tcp拥塞控制是源服务器和客户端控制的,wireguard只扮演数据包转发角色。

找遍了全网也没找到怎么解决,于是动脑尝试解决一下。

透明代理

https://en.wikipedia.org/wiki/Proxy_server#Transparent_proxy

透明代理是一种简单拦截应用层数据的方式。在本文中,我使用透明代理拦截经过wireguard的tcp数据包,使他通过系统TCP协议栈,从而可以获得BBR拥塞控制的好处。

配置路由表和iptables

以下是根据V2ray中透明代理教程修改而来的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 将进入透明代理的流量转发到lo
ip rule add fwmark 1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
ip -6 rule add fwmark 1 table 100
ip -6 route add local ::/0 dev lo table 100

iptables -t mangle -N V2RAY
ip6tables -t mangle -N V2RAY

# 设置透明代理
ip6tables -t mangle -A V2RAY -s fc00:ffff::/64 -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A V2RAY -s 192.168.254.0/24 -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1

iptables -t mangle -A PREROUTING -j V2RAY
ip6tables -t mangle -A PREROUTING -j V2RAY

其中fc00:ffff::/64192.168.254.0/24是wireguard客户端的子网;只有从wireguard客户端发出的流量经过透明代理;UDP不需要透明代理来获得加速。

开发代理程序

可以直接使用V2ray的透明代理功能,但截至本文发出时V2ray的这个功能存在内存泄露问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"inbounds": [
{
"port": 12345,
"protocol": "dokodemo-door",
"settings": {
"network": "tcp,udp",
"followRedirect": true
},
"streamSettings": {
"sockopt": {
"tproxy": "tproxy",
"mark":255
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"streamSettings": {
"sockopt": {
"mark": 255
}
}
}
]
}

因为内存泄露问题,我也写了一个简单的小程序完成这个工作,参考: https://blog.2exp.net/posts/391460368.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let server_addr = env::var("LISTEN").unwrap_or_else(|_| ":::12345".to_string());

println!("Listening on: {}", server_addr);

let listener = TcpListener::bind(server_addr).await?;

setsockopt(&listener, sockopt::IpTransparent, &true).unwrap();
setsockopt(&listener, sockopt::ReuseAddr, &true).unwrap();

loop {
let (mut inbound, addr) = listener.accept().await?;
if let Ok(original) = getsockname::<SockaddrIn6>(inbound.as_fd().as_raw_fd()) {
tokio::spawn(async move {
if let Ok(mut outbound) = TcpStream::connect((original.ip(),original.port()))
.await
{
println!("{} --> {}:{}",addr,original.ip(),original.port());
let _ = setsockopt(&outbound, sockopt::Mark, &254);
copy_bidirectional(&mut inbound, &mut outbound)
.map(|r| {
if let Err(e) = r {
println!("Failed to transfer; error={}", e);
}
})
.await
}
});
}
}
}

依赖如下

1
2
3
4
5
[dependencies]
futures = "0.3.28"
nix = {version = "0.27.1",features = ["net","socket"]}
tokio = { version = "1.28.2", features = ["rt-multi-thread", "net","macros","io-util","time","sync"] }