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
+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(())