Skip to content

Commit

Permalink
add wireguard tunnel (#42)
Browse files Browse the repository at this point in the history
peers can connect with each other using wireguard protocol.
  • Loading branch information
KKRainbow committed Mar 28, 2024
1 parent ce889e9 commit 90110aa
Show file tree
Hide file tree
Showing 13 changed files with 799 additions and 62 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async-stream = "0.3.5"
async-trait = "0.1.74"

dashmap = "5.5.3"
timedmap = "1.0.1"
timedmap = "=1.0.1"

# for tap device
tun = { version = "0.6.1", features = ["async"] }
Expand Down Expand Up @@ -112,6 +112,9 @@ network-interface = "1.1.1"
# for ospf route
pathfinding = "4.9.1"

# for encryption
boringtun = { version = "0.6.0" }

# for cli
tabled = "0.15.*"
humansize = "2.1.3"
Expand All @@ -135,6 +138,7 @@ zip = "0.6.6"

[dev-dependencies]
serial_test = "3.0.0"
rstest = "0.18.2"

[profile.dev]
panic = "abort"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

[简体中文](/README_CN.md) | [English](/README.md)

EasyTier is a simple, plug-and-play, decentralized VPN networking solution implemented with the Rust language and Tokio framework.
EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework.

## Features

- **Decentralized**: No need to rely on centralized services, nodes are equal and independent.
- **Safe**: Use WireGuard protocol to encrypt data.
- **Cross-platform**: Supports MacOS/Linux/Windows, will support IOS and Android in the future. The executable file is statically linked, making deployment simple.
- **Networking without public IP**: Supports networking using shared public nodes, refer to [Configuration Guide](#Networking-without-public-IP)
- **NAT traversal**: Supports UDP-based NAT traversal, able to establish stable connections even in complex network environments.
Expand Down
5 changes: 3 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

[简体中文](/README_CN.md) | [English](/README.md)

一个简单、即插即用、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。

## 特点

- **去中心化**:无需依赖中心化服务,节点平等且独立。
- **安全**:支持利用 WireGuard 加密通信。
- **跨平台**:支持 MacOS/Linux/Windows,未来将支持 IOS 和 Android。可执行文件静态链接,部署简单。
- **无公网 IP 组网**:支持利用共享的公网节点组网,可参考 [配置指南](#无公网IP组网)
- **NAT 穿透**:支持基于 UDP 的 NAT 穿透,即使在复杂的网络环境下也能建立稳定的连接。
Expand Down Expand Up @@ -178,7 +179,7 @@ sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -
# 路线图

- [ ] 完善文档和用户指南。
- [ ] 支持加密、TCP 打洞等特性。
- [ ] 支持 TCP 打洞等特性。
- [ ] 支持 Android、IOS 等移动平台。
- [ ] 支持 Web 配置管理。

Expand Down
2 changes: 1 addition & 1 deletion src/connector/direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ impl DirectConnectorManager {
return Err(Error::UrlInBlacklist);
}

let connector = create_connector_by_url(&addr, data.global_ctx.get_ip_collector()).await?;
let connector = create_connector_by_url(&addr, &data.global_ctx).await?;
let (peer_id, conn_id) = timeout(
std::time::Duration::from_secs(5),
data.peer_manager.try_connect(connector),
Expand Down
2 changes: 1 addition & 1 deletion src/connector/manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl ManualConnectorManager {
}

pub async fn add_connector_by_url(&self, url: &str) -> Result<(), Error> {
self.add_connector(create_connector_by_url(url, self.global_ctx.get_ip_collector()).await?);
self.add_connector(create_connector_by_url(url, &self.global_ctx).await?);
Ok(())
}

Expand Down
35 changes: 27 additions & 8 deletions src/connector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ use std::{
};

use crate::{
common::{error::Error, network::IPCollector},
common::{error::Error, global_ctx::ArcGlobalCtx, network::IPCollector},
tunnels::{
ring_tunnel::RingTunnelConnector, tcp_tunnel::TcpTunnelConnector,
udp_tunnel::UdpTunnelConnector, TunnelConnector,
ring_tunnel::RingTunnelConnector,
tcp_tunnel::TcpTunnelConnector,
udp_tunnel::UdpTunnelConnector,
wireguard::{WgConfig, WgTunnelConnector},
TunnelConnector,
},
};

Expand Down Expand Up @@ -41,31 +44,47 @@ async fn set_bind_addr_for_peer_connector(

pub async fn create_connector_by_url(
url: &str,
ip_collector: Arc<IPCollector>,
global_ctx: &ArcGlobalCtx,
) -> Result<Box<dyn TunnelConnector + Send + Sync + 'static>, Error> {
let url = url::Url::parse(url).map_err(|_| Error::InvalidUrl(url.to_owned()))?;
match url.scheme() {
"tcp" => {
let dst_addr =
crate::tunnels::check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp")?;
let mut connector = TcpTunnelConnector::new(url);
set_bind_addr_for_peer_connector(&mut connector, dst_addr.is_ipv4(), &ip_collector)
.await;
set_bind_addr_for_peer_connector(
&mut connector,
dst_addr.is_ipv4(),
&global_ctx.get_ip_collector(),
)
.await;
return Ok(Box::new(connector));
}
"udp" => {
let dst_addr =
crate::tunnels::check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp")?;
let mut connector = UdpTunnelConnector::new(url);
set_bind_addr_for_peer_connector(&mut connector, dst_addr.is_ipv4(), &ip_collector)
.await;
set_bind_addr_for_peer_connector(
&mut connector,
dst_addr.is_ipv4(),
&global_ctx.get_ip_collector(),
)
.await;
return Ok(Box::new(connector));
}
"ring" => {
crate::tunnels::check_scheme_and_get_socket_addr::<uuid::Uuid>(&url, "ring")?;
let connector = RingTunnelConnector::new(url);
return Ok(Box::new(connector));
}
"wg" => {
crate::tunnels::check_scheme_and_get_socket_addr::<SocketAddr>(&url, "wg")?;
let nid = global_ctx.get_network_identity();
let wg_config =
WgConfig::new_from_network_identity(&nid.network_name, &nid.network_secret);
let connector = WgTunnelConnector::new(url, wg_config);
return Ok(Box::new(connector));
}
_ => {
return Err(Error::InvalidUrl(url.into()));
}
Expand Down
3 changes: 2 additions & 1 deletion src/easytier-core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ struct Cli {

#[arg(short, long, help = "listeners to accept connections, pass '' to avoid listening.",
default_values_t = ["tcp://0.0.0.0:11010".to_string(),
"udp://0.0.0.0:11010".to_string()])]
"udp://0.0.0.0:11010".to_string(),
"wg://0.0.0.0:11011".to_string()])]
listeners: Vec<String>,

/// specify the linux network namespace, default is the root namespace
Expand Down
14 changes: 12 additions & 2 deletions src/instance/listeners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ use crate::{
},
peers::peer_manager::PeerManager,
tunnels::{
ring_tunnel::RingTunnelListener, tcp_tunnel::TcpTunnelListener,
udp_tunnel::UdpTunnelListener, Tunnel, TunnelListener,
ring_tunnel::RingTunnelListener,
tcp_tunnel::TcpTunnelListener,
udp_tunnel::UdpTunnelListener,
wireguard::{WgConfig, WgTunnelListener},
Tunnel, TunnelListener,
},
};

Expand Down Expand Up @@ -66,6 +69,13 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
"udp" => {
self.add_listener(UdpTunnelListener::new(l.clone())).await?;
}
"wg" => {
let nid = self.global_ctx.get_network_identity();
let wg_config =
WgConfig::new_from_network_identity(&nid.network_name, &nid.network_secret);
self.add_listener(WgTunnelListener::new(l.clone(), wg_config))
.await?;
}
_ => {
log::warn!("unsupported listener uri: {}", l);
}
Expand Down
112 changes: 73 additions & 39 deletions src/tests/three_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::{
ring_tunnel::RingTunnelConnector,
tcp_tunnel::{TcpTunnelConnector, TcpTunnelListener},
udp_tunnel::{UdpTunnelConnector, UdpTunnelListener},
wireguard::{WgConfig, WgTunnelConnector},
},
};

Expand Down Expand Up @@ -50,6 +51,7 @@ pub fn get_inst_config(inst_name: &str, ns: Option<&str>, ipv4: &str) -> TomlCon
config.set_listeners(vec![
"tcp://0.0.0.0:11010".parse().unwrap(),
"udp://0.0.0.0:11010".parse().unwrap(),
"wg://0.0.0.0:11011".parse().unwrap(),
]);
config
}
Expand All @@ -72,12 +74,22 @@ pub async fn init_three_node(proto: &str) -> Vec<Instance> {
.add_connector(TcpTunnelConnector::new(
"tcp://10.1.1.1:11010".parse().unwrap(),
));
} else {
} else if proto == "udp" {
inst2
.get_conn_manager()
.add_connector(UdpTunnelConnector::new(
"udp://10.1.1.1:11010".parse().unwrap(),
));
} else if proto == "wg" {
inst2
.get_conn_manager()
.add_connector(WgTunnelConnector::new(
"wg://10.1.1.1:11011".parse().unwrap(),
WgConfig::new_from_network_identity(
&inst1.get_global_ctx().get_network_identity().network_name,
&inst1.get_global_ctx().get_network_identity().network_secret,
),
));
}

inst2
Expand All @@ -101,10 +113,11 @@ pub async fn init_three_node(proto: &str) -> Vec<Instance> {
vec![inst1, inst2, inst3]
}

#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn basic_three_node_test_tcp() {
let insts = init_three_node("tcp").await;
pub async fn basic_three_node_test(#[values("tcp", "udp", "wg")] proto: &str) {
let insts = init_three_node(proto).await;

check_route(
"10.144.144.2",
Expand All @@ -119,28 +132,11 @@ pub async fn basic_three_node_test_tcp() {
);
}

#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn basic_three_node_test_udp() {
let insts = init_three_node("udp").await;

check_route(
"10.144.144.2",
insts[1].peer_id(),
insts[0].get_peer_manager().list_routes().await,
);

check_route(
"10.144.144.3",
insts[2].peer_id(),
insts[0].get_peer_manager().list_routes().await,
);
}

#[tokio::test]
#[serial_test::serial]
pub async fn tcp_proxy_three_node_test() {
let insts = init_three_node("tcp").await;
pub async fn tcp_proxy_three_node_test(#[values("tcp", "udp", "wg")] proto: &str) {
let insts = init_three_node(proto).await;

insts[2]
.get_global_ctx()
Expand Down Expand Up @@ -171,10 +167,11 @@ pub async fn tcp_proxy_three_node_test() {
.await;
}

#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn icmp_proxy_three_node_test() {
let insts = init_three_node("tcp").await;
pub async fn icmp_proxy_three_node_test(#[values("tcp", "udp", "wg")] proto: &str) {
let insts = init_three_node(proto).await;

insts[2]
.get_global_ctx()
Expand Down Expand Up @@ -205,34 +202,71 @@ pub async fn icmp_proxy_three_node_test() {
assert_eq!(code.code().unwrap(), 0);
}

#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn proxy_three_node_disconnect_test() {
pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str) {
let insts = init_three_node(proto).await;
let mut inst4 = Instance::new(get_inst_config("inst4", Some("net_d"), "10.144.144.4"));
inst4
.get_conn_manager()
.add_connector(TcpTunnelConnector::new(
"tcp://10.1.2.3:11010".parse().unwrap(),
));
if proto == "tcp" {
inst4
.get_conn_manager()
.add_connector(TcpTunnelConnector::new(
"tcp://10.1.2.3:11010".parse().unwrap(),
));
} else if proto == "wg" {
inst4
.get_conn_manager()
.add_connector(WgTunnelConnector::new(
"wg://10.1.2.3:11011".parse().unwrap(),
WgConfig::new_from_network_identity(
&inst4.get_global_ctx().get_network_identity().network_name,
&inst4.get_global_ctx().get_network_identity().network_secret,
),
));
} else {
unreachable!("not support");
}
inst4.run().await.unwrap();

tokio::spawn(async {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
let task = tokio::spawn(async move {
for _ in 1..=2 {
tokio::time::sleep(tokio::time::Duration::from_secs(8)).await;
// inst4 should be in inst1's route list
let routes = insts[0].get_peer_manager().list_routes().await;
assert!(
routes
.iter()
.find(|r| r.peer_id == inst4.peer_id())
.is_some(),
"inst4 should be in inst1's route list, {:?}",
routes
);

set_link_status("net_d", false);
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
tokio::time::sleep(tokio::time::Duration::from_secs(8)).await;
let routes = insts[0].get_peer_manager().list_routes().await;
assert!(
routes
.iter()
.find(|r| r.peer_id == inst4.peer_id())
.is_none(),
"inst4 should not be in inst1's route list, {:?}",
routes
);
set_link_status("net_d", true);
}
});

// TODO: add some traffic here, also should check route & peer list
tokio::time::sleep(tokio::time::Duration::from_secs(35)).await;
let (ret,) = tokio::join!(task);
assert!(ret.is_ok());
}

#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn udp_proxy_three_node_test() {
let insts = init_three_node("tcp").await;
pub async fn udp_proxy_three_node_test(#[values("tcp", "udp", "wg")] proto: &str) {
let insts = init_three_node(proto).await;

insts[2]
.get_global_ctx()
Expand Down
2 changes: 1 addition & 1 deletion src/tunnels/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ pub mod tests {

close_tunnel(&tunnel).await.unwrap();

if connector.remote_url().scheme() == "udp" {
if ["udp", "wg"].contains(&connector.remote_url().scheme()) {
lis.abort();
} else {
// lis should finish in 1 second
Expand Down
1 change: 1 addition & 0 deletions src/tunnels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod stats;
pub mod tcp_tunnel;
pub mod tunnel_filter;
pub mod udp_tunnel;
pub mod wireguard;

use std::{fmt::Debug, net::SocketAddr, pin::Pin, sync::Arc};

Expand Down
Loading

0 comments on commit 90110aa

Please sign in to comment.