前言 众所周知,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 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::/64
和192.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" ] }