Add support for IPv6 within VPN (#1061)

* add flake.nix with nix based dev shell
* add support for IPv6
* update thunk

---------

Co-authored-by: sijie.sun <sijie.sun@smartx.com>
This commit is contained in:
DavHau
2025-07-04 22:43:30 +07:00
committed by GitHub
parent 01e491ec07
commit d0cfc49806
32 changed files with 893 additions and 70 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ pub async fn prepare_env(dns_name: &str, tun_ip: Ipv4Inet) -> (Arc<PeerManager>,
let r = Arc::new(tokio::sync::Mutex::new(r));
let mut virtual_nic = NicCtx::new(peer_mgr.get_global_ctx(), &peer_mgr, r);
virtual_nic.run(tun_ip).await.unwrap();
virtual_nic.run(Some(tun_ip), None).await.unwrap();
(peer_mgr, virtual_nic)
}
+25 -20
View File
@@ -484,7 +484,7 @@ impl Instance {
&peer_manager_c,
_peer_packet_receiver.clone(),
);
if let Err(e) = new_nic_ctx.run(ip).await {
if let Err(e) = new_nic_ctx.run(Some(ip), global_ctx_c.get_ipv6()).await {
tracing::error!(
?current_dhcp_ip,
?candidate_ipv4_addr,
@@ -532,24 +532,29 @@ impl Instance {
if !self.global_ctx.config.get_flags().no_tun {
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
if let Some(ipv4_addr) = self.global_ctx.get_ipv4() {
let mut new_nic_ctx = NicCtx::new(
self.global_ctx.clone(),
&self.peer_manager,
self.peer_packet_receiver.clone(),
);
new_nic_ctx.run(ipv4_addr).await?;
let ifname = new_nic_ctx.ifname().await;
Self::use_new_nic_ctx(
self.nic_ctx.clone(),
new_nic_ctx,
Self::create_magic_dns_runner(
self.peer_manager.clone(),
ifname,
ipv4_addr.clone(),
),
)
.await;
{
let ipv4_addr = self.global_ctx.get_ipv4();
let ipv6_addr = self.global_ctx.get_ipv6();
// Only run if we have at least one IP address (IPv4 or IPv6)
if ipv4_addr.is_some() || ipv6_addr.is_some() {
let mut new_nic_ctx = NicCtx::new(
self.global_ctx.clone(),
&self.peer_manager,
self.peer_packet_receiver.clone(),
);
new_nic_ctx.run(ipv4_addr, ipv6_addr).await?;
let ifname = new_nic_ctx.ifname().await;
// Create Magic DNS runner only if we have IPv4
let dns_runner = if let Some(ipv4) = ipv4_addr {
Self::create_magic_dns_runner(self.peer_manager.clone(), ifname, ipv4)
} else {
None
};
Self::use_new_nic_ctx(self.nic_ctx.clone(), new_nic_ctx, dns_runner).await;
}
}
}
@@ -852,7 +857,7 @@ impl Drop for Instance {
};
let now = std::time::Instant::now();
while now.elapsed().as_secs() < 1 {
while now.elapsed().as_secs() < 10 {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
if pm.strong_count() == 0 {
tracing::info!(
+92 -10
View File
@@ -1,7 +1,7 @@
use std::{
collections::BTreeSet,
io,
net::Ipv4Addr,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
pin::Pin,
sync::{Arc, Weak},
task::{Context, Poll},
@@ -25,7 +25,7 @@ use byteorder::WriteBytesExt as _;
use bytes::{BufMut, BytesMut};
use futures::{lock::BiLock, ready, SinkExt, Stream, StreamExt};
use pin_project_lite::pin_project;
use pnet::packet::ipv4::Ipv4Packet;
use pnet::packet::{ipv4::Ipv4Packet, ipv6::Ipv6Packet};
use tokio::{
io::{AsyncRead, AsyncWrite, ReadBuf},
sync::Mutex,
@@ -434,12 +434,26 @@ impl VirtualNic {
Ok(())
}
pub async fn add_ipv6_route(&self, address: Ipv6Addr, cidr: u8) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg
.add_ipv6_route(self.ifname(), address, cidr, None)
.await?;
Ok(())
}
pub async fn remove_ip(&self, ip: Option<Ipv4Addr>) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg.remove_ip(self.ifname(), ip).await?;
Ok(())
}
pub async fn remove_ipv6(&self, ip: Option<Ipv6Addr>) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg.remove_ipv6(self.ifname(), ip).await?;
Ok(())
}
pub async fn add_ip(&self, ip: Ipv4Addr, cidr: i32) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg
@@ -448,6 +462,14 @@ impl VirtualNic {
Ok(())
}
pub async fn add_ipv6(&self, ip: Ipv6Addr, cidr: i32) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg
.add_ipv6_ip(self.ifname(), ip, cidr as u8)
.await?;
Ok(())
}
pub fn get_ifcfg(&self) -> impl IfConfiguerTrait {
IfConfiger {}
}
@@ -496,6 +518,20 @@ impl NicCtx {
Ok(())
}
pub async fn assign_ipv6_to_tun_device(&self, ipv6_addr: cidr::Ipv6Inet) -> Result<(), Error> {
let nic = self.nic.lock().await;
nic.link_up().await?;
nic.remove_ipv6(None).await?;
nic.add_ipv6(ipv6_addr.address(), ipv6_addr.network_length() as i32)
.await?;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
{
nic.add_ipv6_route(ipv6_addr.first_address(), ipv6_addr.network_length())
.await?;
}
Ok(())
}
async fn do_forward_nic_to_peers_ipv4(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv4) = Ipv4Packet::new(ret.payload()) {
if ipv4.get_version() != 4 {
@@ -509,16 +545,53 @@ impl NicCtx {
);
// TODO: use zero-copy
let send_ret = mgr.send_msg_ipv4(ret, dst_ipv4).await;
let send_ret = mgr.send_msg_by_ip(ret, IpAddr::V4(dst_ipv4)).await;
if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg_ipv4 failed")
tracing::trace!(?send_ret, "[USER_PACKET] send_msg failed")
}
} else {
tracing::warn!(?ret, "[USER_PACKET] not ipv4 packet");
}
}
fn do_forward_nic_to_peers(
async fn do_forward_nic_to_peers_ipv6(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv6) = Ipv6Packet::new(ret.payload()) {
if ipv6.get_version() != 6 {
tracing::info!("[USER_PACKET] not ipv6 packet: {:?}", ipv6);
return;
}
let dst_ipv6 = ipv6.get_destination();
tracing::trace!(
?ret,
"[USER_PACKET] recv new packet from tun device and forward to peers."
);
// TODO: use zero-copy
let send_ret = mgr.send_msg_by_ip(ret, IpAddr::V6(dst_ipv6)).await;
if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg failed")
}
} else {
tracing::warn!(?ret, "[USER_PACKET] not ipv6 packet");
}
}
async fn do_forward_nic_to_peers(ret: ZCPacket, mgr: &PeerManager) {
let payload = ret.payload();
if payload.is_empty() {
return;
}
match payload[0] >> 4 {
4 => Self::do_forward_nic_to_peers_ipv4(ret, mgr).await,
6 => Self::do_forward_nic_to_peers_ipv6(ret, mgr).await,
_ => {
tracing::warn!(?ret, "[USER_PACKET] unknown IP version");
}
}
}
fn do_forward_nic_to_peers_task(
&mut self,
mut stream: Pin<Box<dyn ZCPacketStream>>,
) -> Result<(), Error> {
@@ -532,7 +605,7 @@ impl NicCtx {
tracing::error!("read from nic failed: {:?}", ret);
break;
}
Self::do_forward_nic_to_peers_ipv4(ret.unwrap(), mgr.as_ref()).await;
Self::do_forward_nic_to_peers(ret.unwrap(), mgr.as_ref()).await;
}
panic!("nic stream closed");
});
@@ -647,7 +720,7 @@ impl NicCtx {
Ok(())
}
pub async fn run(&mut self, ipv4_addr: cidr::Ipv4Inet) -> Result<(), Error> {
pub async fn run(&mut self, ipv4_addr: Option<cidr::Ipv4Inet>, ipv6_addr: Option<cidr::Ipv6Inet>) -> Result<(), Error> {
let tunnel = {
let mut nic = self.nic.lock().await;
match nic.create_dev().await {
@@ -681,10 +754,19 @@ impl NicCtx {
let (stream, sink) = tunnel.split();
self.do_forward_nic_to_peers(stream)?;
self.do_forward_nic_to_peers_task(stream)?;
self.do_forward_peers_to_nic(sink);
self.assign_ipv4_to_tun_device(ipv4_addr).await?;
// Assign IPv4 address if provided
if let Some(ipv4_addr) = ipv4_addr {
self.assign_ipv4_to_tun_device(ipv4_addr).await?;
}
// Assign IPv6 address if provided
if let Some(ipv6_addr) = ipv6_addr {
self.assign_ipv6_to_tun_device(ipv6_addr).await?;
}
self.run_proxy_cidrs_route_updater().await?;
Ok(())
@@ -710,7 +792,7 @@ impl NicCtx {
let (stream, sink) = tunnel.split();
self.do_forward_nic_to_peers(stream)?;
self.do_forward_nic_to_peers_task(stream)?;
self.do_forward_peers_to_nic(sink);
Ok(())