fix(shared-tun): close mobile routing gaps

Translate IPv6 traffic across shared mobile members, including
transport checksums and reverse-flow handling.

Normalize saved Android TUN configs and detach runtimes before the
VPN fd closes. Restore the mainline lockfile after resolving the merge.
This commit is contained in:
KKRainbow
2026-08-13 01:43:23 +08:00
parent 144e306562
commit b5bc4a853d
4 changed files with 750 additions and 49 deletions
+61 -7
View File
@@ -73,7 +73,7 @@ static RPC_SERVER: once_cell::sync::Lazy<Mutex<Option<RpcServer>>> =
static WEB_CLIENT: once_cell::sync::Lazy<RwLock<Option<WebClient>>> =
once_cell::sync::Lazy::new(|| RwLock::new(None));
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", test))]
const ANDROID_SHARED_TUN_DEV_NAME: &str = "easytier-shared";
macro_rules! get_client_manager {
@@ -88,15 +88,18 @@ macro_rules! get_client_manager {
fn normalize_network_config_for_runtime(cfg: &mut NetworkConfig) {
#[cfg(target_os = "android")]
{
if !cfg.no_tun() && cfg.dev_name.as_deref().map(str::is_empty).unwrap_or(true) {
cfg.dev_name = Some(ANDROID_SHARED_TUN_DEV_NAME.to_owned());
}
}
normalize_android_network_config(cfg);
#[cfg(not(target_os = "android"))]
let _ = cfg;
}
#[cfg(any(target_os = "android", test))]
fn normalize_android_network_config(cfg: &mut NetworkConfig) {
if !cfg.no_tun() && cfg.dev_name.as_deref().map(str::is_empty).unwrap_or(true) {
cfg.dev_name = Some(ANDROID_SHARED_TUN_DEV_NAME.to_owned());
}
}
#[tauri::command]
fn easytier_version() -> Result<String, String> {
Ok(easytier::VERSION.to_string())
@@ -252,6 +255,31 @@ async fn set_tun_fd(
return Err("duplicate instance id in TUN fd attachment group".to_string());
}
#[cfg(any(target_os = "android", target_os = "ios"))]
if fd <= 0 {
let mut errors = Vec::new();
for instance_id in target_ids {
if let Err(error) = attach_mobile_tun_fd(
instance_manager.as_ref(),
instance_id,
fd,
easytier::instance::virtual_nic::MobileTunSources::default(),
)
.await
{
errors.push(format!("{instance_id}: {error}"));
}
}
return if errors.is_empty() {
Ok(())
} else {
Err(format!(
"failed to detach tun fd from the instance group: {}",
errors.join("; ")
))
};
}
#[cfg(any(target_os = "android", target_os = "ios"))]
let mut source_map = parse_tun_fd_instance_sources(instance_sources.unwrap_or_default())?;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -955,9 +983,10 @@ mod manager {
&self,
app: &AppHandle,
inst_id: Uuid,
cfg: NetworkConfig,
mut cfg: NetworkConfig,
source: PersistedConfigSource,
) -> anyhow::Result<()> {
normalize_network_config_for_runtime(&mut cfg);
let source = self
.network_configs
.get(&inst_id)
@@ -1436,8 +1465,33 @@ mod manager {
#[cfg(test)]
mod tests {
use super::{PersistedConfigSource, StoredGuiConfig};
use crate::{ANDROID_SHARED_TUN_DEV_NAME, normalize_android_network_config};
use easytier::proto::api::manage::NetworkConfig;
#[test]
fn android_default_tun_config_uses_shared_device_name() {
let mut config = NetworkConfig::default();
normalize_android_network_config(&mut config);
assert_eq!(
config.dev_name.as_deref(),
Some(ANDROID_SHARED_TUN_DEV_NAME)
);
}
#[test]
fn android_no_tun_config_keeps_empty_device_name() {
let mut config = NetworkConfig {
no_tun: Some(true),
..Default::default()
};
normalize_android_network_config(&mut config);
assert!(config.dev_name.is_none());
}
#[test]
fn stored_gui_config_defaults_missing_source_to_legacy() {
let stored: StoredGuiConfig = serde_json::from_value(serde_json::json!({
@@ -100,11 +100,23 @@ async function waitVpnStatus(target_status: boolean, timeout_sec: number) {
}
}
async function detachTunFd(instanceIds: string[], instanceSources: TunFdInstanceSources[]) {
try {
await setTunFd(0, instanceIds, instanceSources)
}
catch (e) {
console.error('detach tun fd failed', e)
}
}
async function doStopVpn(force = false) {
const wasRunning = curVpnStatus.running
if (!force && !wasRunning) {
return
}
const instanceIds = [...curVpnStatus.instanceIds]
const instanceSources = [...curVpnStatus.instanceSources]
await detachTunFd(instanceIds, instanceSources)
console.log('stop vpn')
const stop_ret = await stop_vpn()
console.log('stop vpn', JSON.stringify((stop_ret)))
@@ -194,6 +206,9 @@ async function onVpnServiceStart(payload: any) {
async function onVpnServiceStop(payload: any) {
console.log('vpn service stop', JSON.stringify(payload))
const instanceIds = [...curVpnStatus.instanceIds]
const instanceSources = [...curVpnStatus.instanceSources]
await detachTunFd(instanceIds, instanceSources)
curVpnStatus.running = false
resetVpnConfigStatus()
}
@@ -15,6 +15,7 @@ use pnet_packet::{
icmp::{self, MutableIcmpPacket},
ip::IpNextHeaderProtocols,
ipv4::{self, MutableIpv4Packet},
ipv6::MutableIpv6Packet,
tcp::{self, MutableTcpPacket},
udp::{self, MutableUdpPacket},
};
@@ -48,6 +49,7 @@ const TCP_HEADER_MIN_LEN: usize = 20;
const UDP_HEADER_LEN: usize = 8;
const ICMP_ECHO_HEADER_LEN: usize = 8;
const ICMP_PROTOCOL: u8 = 1;
const ICMPV6_PROTOCOL: u8 = 58;
const TCP_PROTOCOL: u8 = 6;
const UDP_PROTOCOL: u8 = 17;
const DISPATCHER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1);
@@ -372,6 +374,13 @@ impl SharedVirtualNicFlowAddr {
Self::V6(_) => None,
}
}
fn as_ipv6(self) -> Option<std::net::Ipv6Addr> {
match self {
Self::V4(_) => None,
Self::V6(addr) => Some(std::net::Ipv6Addr::from(addr)),
}
}
}
impl SharedVirtualNicFlowKey {
@@ -416,12 +425,16 @@ impl SharedVirtualNicFlowKey {
return None;
}
let protocol = payload[6];
let transport = ipv6_transport_info(payload)?;
Some(Self {
src: SharedVirtualNicFlowAddr::V6(read_ipv6_addr(payload, 8)),
dst: SharedVirtualNicFlowAddr::V6(read_ipv6_addr(payload, 24)),
protocol,
ports: transport_ports(protocol, &payload[IPV6_HEADER_LEN..]),
protocol: transport.protocol,
ports: if transport.fragment_offset == 0 {
transport_ports(transport.protocol, &payload[transport.offset..])
} else {
None
},
})
}
@@ -431,7 +444,7 @@ impl SharedVirtualNicFlowKey {
dst: self.src,
protocol: self.protocol,
ports: self.ports.map(|ports| {
if self.protocol == ICMP_PROTOCOL {
if matches!(self.protocol, ICMP_PROTOCOL | ICMPV6_PROTOCOL) {
ports
} else {
ports.reversed()
@@ -1231,21 +1244,27 @@ impl SharedVirtualNicDispatcherState {
member_id: SharedVirtualNicMemberId,
mut packet: ZCPacket,
) -> Result<(), ZCPacket> {
let mut nat_entry = None;
if let Some(key) = SharedVirtualNicFlowKey::from_packet(&packet) {
if let Some(translated_src) = self
.source_table
.source_for_member_destination(member_id, key.dst)
{
if key.src != translated_src
&& rewrite_packet_source(&mut packet, key.src, translated_src)
{
nat_entry = Some((packet.clone(), key.src, translated_src));
}
}
}
let Some(key) = SharedVirtualNicFlowKey::from_packet(&packet) else {
return Err(packet);
};
let Some(translated_src) = self
.source_table
.source_for_member_destination(member_id, key.dst)
else {
return Err(packet);
};
let original_packet = packet.clone();
let nat_entry = if key.src == translated_src {
None
} else if rewrite_packet_source(&mut packet, key.src, translated_src) {
Some((packet.clone(), key.src, translated_src))
} else {
return Err(packet);
};
self.send_packet_to_member(member_id, packet).await?;
if self.send_packet_to_member(member_id, packet).await.is_err() {
return Err(original_packet);
}
if let Some((translated_packet, original_src, translated_src)) = nat_entry {
self.nat_table
.remember(&translated_packet, original_src, translated_src);
@@ -1304,7 +1323,7 @@ fn rewrite_packet_source(
expected_source: SharedVirtualNicFlowAddr,
new_source: SharedVirtualNicFlowAddr,
) -> bool {
rewrite_ipv4_addr(packet, expected_source, new_source, RewriteIpv4Addr::Source)
rewrite_ip_addr(packet, expected_source, new_source, RewriteIpAddr::Source)
}
fn rewrite_packet_destination(
@@ -1312,25 +1331,42 @@ fn rewrite_packet_destination(
expected_destination: SharedVirtualNicFlowAddr,
new_destination: SharedVirtualNicFlowAddr,
) -> bool {
rewrite_ipv4_addr(
rewrite_ip_addr(
packet,
expected_destination,
new_destination,
RewriteIpv4Addr::Destination,
RewriteIpAddr::Destination,
)
}
#[derive(Clone, Copy)]
enum RewriteIpv4Addr {
enum RewriteIpAddr {
Source,
Destination,
}
fn rewrite_ip_addr(
packet: &mut ZCPacket,
expected_addr: SharedVirtualNicFlowAddr,
new_addr: SharedVirtualNicFlowAddr,
rewrite: RewriteIpAddr,
) -> bool {
match (expected_addr, new_addr) {
(SharedVirtualNicFlowAddr::V4(_), SharedVirtualNicFlowAddr::V4(_)) => {
rewrite_ipv4_addr(packet, expected_addr, new_addr, rewrite)
}
(SharedVirtualNicFlowAddr::V6(_), SharedVirtualNicFlowAddr::V6(_)) => {
rewrite_ipv6_addr(packet, expected_addr, new_addr, rewrite)
}
_ => false,
}
}
fn rewrite_ipv4_addr(
packet: &mut ZCPacket,
expected_addr: SharedVirtualNicFlowAddr,
new_addr: SharedVirtualNicFlowAddr,
rewrite: RewriteIpv4Addr,
rewrite: RewriteIpAddr,
) -> bool {
let Some(expected_addr) = expected_addr.as_ipv4() else {
return false;
@@ -1355,10 +1391,10 @@ fn rewrite_ipv4_addr(
|| (ipv4_packet.get_flags() & ipv4::Ipv4Flags::MoreFragments) != 0;
match rewrite {
RewriteIpv4Addr::Source if ipv4_packet.get_source() == expected_addr => {
RewriteIpAddr::Source if ipv4_packet.get_source() == expected_addr => {
ipv4_packet.set_source(new_addr);
}
RewriteIpv4Addr::Destination if ipv4_packet.get_destination() == expected_addr => {
RewriteIpAddr::Destination if ipv4_packet.get_destination() == expected_addr => {
ipv4_packet.set_destination(new_addr);
}
_ => return false,
@@ -1380,6 +1416,154 @@ fn rewrite_ipv4_addr(
true
}
fn rewrite_ipv6_addr(
packet: &mut ZCPacket,
expected_addr: SharedVirtualNicFlowAddr,
new_addr: SharedVirtualNicFlowAddr,
rewrite: RewriteIpAddr,
) -> bool {
let Some(expected_addr) = expected_addr.as_ipv6() else {
return false;
};
let Some(new_addr) = new_addr.as_ipv6() else {
return false;
};
let payload = packet.mut_payload();
let Some(transport) = ipv6_transport_info(payload) else {
return false;
};
if matches!(transport.protocol, 50 | 51)
|| (matches!(rewrite, RewriteIpAddr::Destination) && !transport.destination_rewrite_safe)
{
return false;
}
let Some(mut ipv6_packet) = MutableIpv6Packet::new(payload) else {
return false;
};
let old_source = ipv6_packet.get_source();
let old_destination = ipv6_packet.get_destination();
match rewrite {
RewriteIpAddr::Source if old_source == expected_addr => {
ipv6_packet.set_source(new_addr);
}
RewriteIpAddr::Destination if old_destination == expected_addr => {
ipv6_packet.set_destination(new_addr);
}
_ => return false,
}
if transport.fragment_offset == 0 {
let source = ipv6_packet.get_source();
let destination = ipv6_packet.get_destination();
adjust_ipv6_transport_checksum(
ipv6_packet.packet_mut(),
transport,
old_source,
source,
old_destination,
destination,
);
}
true
}
#[derive(Clone, Copy)]
struct Ipv6TransportInfo {
protocol: u8,
offset: usize,
fragment_offset: u16,
destination_rewrite_safe: bool,
}
fn ipv6_transport_info(payload: &[u8]) -> Option<Ipv6TransportInfo> {
if payload.len() < IPV6_HEADER_LEN || payload[0] >> 4 != 6 {
return None;
}
let mut protocol = payload[6];
let mut offset = IPV6_HEADER_LEN;
let mut fragment_offset = 0;
let mut destination_rewrite_safe = true;
loop {
match protocol {
// Hop-by-hop, routing, and destination options headers.
0 | 43 | 60 => {
if protocol == 43 {
destination_rewrite_safe = false;
}
let header = payload.get(offset..offset + 2)?;
protocol = header[0];
let header_len = (usize::from(header[1]) + 1) * 8;
offset = offset.checked_add(header_len)?;
if offset > payload.len() {
return None;
}
}
// Fragment header.
44 => {
let header = payload.get(offset..offset + 8)?;
protocol = header[0];
fragment_offset = u16::from_be_bytes([header[2], header[3]]) >> 3;
offset += 8;
if fragment_offset != 0 {
return Some(Ipv6TransportInfo {
protocol,
offset,
fragment_offset,
destination_rewrite_safe: destination_rewrite_safe
&& matches!(protocol, TCP_PROTOCOL | UDP_PROTOCOL | ICMPV6_PROTOCOL),
});
}
}
_ => {
return Some(Ipv6TransportInfo {
protocol,
offset,
fragment_offset,
destination_rewrite_safe,
});
}
}
}
}
fn adjust_ipv6_transport_checksum(
payload: &mut [u8],
transport: Ipv6TransportInfo,
old_source: std::net::Ipv6Addr,
source: std::net::Ipv6Addr,
old_destination: std::net::Ipv6Addr,
destination: std::net::Ipv6Addr,
) {
let checksum_offset = match transport.protocol {
TCP_PROTOCOL => 16,
UDP_PROTOCOL => 6,
ICMPV6_PROTOCOL => 2,
_ => return,
};
let Some(checksum_bytes) =
payload.get_mut(transport.offset + checksum_offset..transport.offset + checksum_offset + 2)
else {
return;
};
let checksum = u16::from_be_bytes([checksum_bytes[0], checksum_bytes[1]]);
let checksum = adjust_ipv6_pseudo_header_checksum(
checksum,
old_source,
source,
old_destination,
destination,
);
let checksum = if transport.protocol == UDP_PROTOCOL && checksum == 0 {
0xffff
} else {
checksum
};
checksum_bytes.copy_from_slice(&checksum.to_be_bytes());
}
fn update_ipv4_transport_checksum(ipv4_packet: &mut MutableIpv4Packet<'_>, header_len: usize) {
let source = ipv4_packet.get_source();
let destination = ipv4_packet.get_destination();
@@ -1483,6 +1667,29 @@ fn adjust_ipv4_pseudo_header_checksum(
checksum
}
fn adjust_ipv6_pseudo_header_checksum(
mut checksum: u16,
old_source: std::net::Ipv6Addr,
source: std::net::Ipv6Addr,
old_destination: std::net::Ipv6Addr,
destination: std::net::Ipv6Addr,
) -> u16 {
for (old, new) in old_source
.segments()
.into_iter()
.zip(source.segments())
.chain(
old_destination
.segments()
.into_iter()
.zip(destination.segments()),
)
{
checksum = adjust_checksum_word(checksum, old, new);
}
checksum
}
fn ipv4_checksum_words(addr: std::net::Ipv4Addr) -> impl Iterator<Item = u16> {
let octets = addr.octets();
[
@@ -1753,7 +1960,7 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
let min_len = match protocol {
TCP_PROTOCOL => TCP_HEADER_MIN_LEN,
UDP_PROTOCOL => UDP_HEADER_LEN,
ICMP_PROTOCOL => ICMP_ECHO_HEADER_LEN,
ICMP_PROTOCOL | ICMPV6_PROTOCOL => ICMP_ECHO_HEADER_LEN,
_ => return None,
};
@@ -1763,6 +1970,7 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
match protocol {
ICMP_PROTOCOL => icmp_echo_flow(payload),
ICMPV6_PROTOCOL => icmpv6_echo_flow(payload),
_ => Some(SharedVirtualNicTransportPorts {
src: u16::from_be_bytes([payload[0], payload[1]]),
dst: u16::from_be_bytes([payload[2], payload[3]]),
@@ -1770,6 +1978,16 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
}
}
fn icmpv6_echo_flow(payload: &[u8]) -> Option<SharedVirtualNicTransportPorts> {
match payload[0] {
128 | 129 => Some(SharedVirtualNicTransportPorts {
src: u16::from_be_bytes([payload[4], payload[5]]),
dst: u16::from_be_bytes([payload[6], payload[7]]),
}),
_ => None,
}
}
fn icmp_echo_flow(payload: &[u8]) -> Option<SharedVirtualNicTransportPorts> {
match payload[0] {
ty if ty == icmp::IcmpTypes::EchoRequest.0 || ty == icmp::IcmpTypes::EchoReply.0 => {
@@ -1834,6 +2052,84 @@ mod tests {
ZCPacket::new_with_payload(&payload)
}
fn ipv6_udp_packet_with_ports(
src: Ipv6Addr,
dst: Ipv6Addr,
src_port: u16,
dst_port: u16,
) -> ZCPacket {
let mut payload = vec![0; IPV6_HEADER_LEN + UDP_HEADER_LEN];
{
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
ipv6_packet.set_version(6);
ipv6_packet.set_payload_length(UDP_HEADER_LEN as u16);
ipv6_packet.set_next_header(IpNextHeaderProtocols::Udp);
ipv6_packet.set_hop_limit(64);
ipv6_packet.set_source(src);
ipv6_packet.set_destination(dst);
}
{
let mut udp_packet = MutableUdpPacket::new(&mut payload[IPV6_HEADER_LEN..]).unwrap();
udp_packet.set_source(src_port);
udp_packet.set_destination(dst_port);
udp_packet.set_length(UDP_HEADER_LEN as u16);
let checksum = udp::ipv6_checksum(&udp_packet.to_immutable(), &src, &dst);
udp_packet.set_checksum(checksum);
}
ZCPacket::new_with_payload(&payload)
}
fn ipv6_tcp_packet_with_ports(
src: Ipv6Addr,
dst: Ipv6Addr,
src_port: u16,
dst_port: u16,
) -> ZCPacket {
let mut payload = vec![0; IPV6_HEADER_LEN + TCP_HEADER_MIN_LEN];
{
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
ipv6_packet.set_version(6);
ipv6_packet.set_payload_length(TCP_HEADER_MIN_LEN as u16);
ipv6_packet.set_next_header(IpNextHeaderProtocols::Tcp);
ipv6_packet.set_hop_limit(64);
ipv6_packet.set_source(src);
ipv6_packet.set_destination(dst);
}
{
let mut tcp_packet = MutableTcpPacket::new(&mut payload[IPV6_HEADER_LEN..]).unwrap();
tcp_packet.set_source(src_port);
tcp_packet.set_destination(dst_port);
tcp_packet.set_data_offset(5);
let checksum = tcp::ipv6_checksum(&tcp_packet.to_immutable(), &src, &dst);
tcp_packet.set_checksum(checksum);
}
ZCPacket::new_with_payload(&payload)
}
fn ipv6_icmp_echo_packet(src: Ipv6Addr, dst: Ipv6Addr) -> ZCPacket {
let mut payload = vec![0; IPV6_HEADER_LEN + ICMP_ECHO_HEADER_LEN];
{
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
ipv6_packet.set_version(6);
ipv6_packet.set_payload_length(ICMP_ECHO_HEADER_LEN as u16);
ipv6_packet.set_next_header(IpNextHeaderProtocols::Icmpv6);
ipv6_packet.set_hop_limit(64);
ipv6_packet.set_source(src);
ipv6_packet.set_destination(dst);
}
{
let mut icmp_packet =
pnet_packet::icmpv6::MutableIcmpv6Packet::new(&mut payload[IPV6_HEADER_LEN..])
.unwrap();
icmp_packet.set_icmpv6_type(pnet_packet::icmpv6::Icmpv6Types::EchoRequest);
icmp_packet.packet_mut()[4..6].copy_from_slice(&1234_u16.to_be_bytes());
icmp_packet.packet_mut()[6..8].copy_from_slice(&1_u16.to_be_bytes());
let checksum = pnet_packet::icmpv6::checksum(&icmp_packet.to_immutable(), &src, &dst);
icmp_packet.set_checksum(checksum);
}
ZCPacket::new_with_payload(&payload)
}
fn ipv4_udp_packet(src: Ipv4Addr, dst: Ipv4Addr) -> ZCPacket {
ipv4_udp_packet_with_ports(src, dst, 1234, 5678)
}
@@ -1987,6 +2283,13 @@ mod tests {
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], &[]))
}
fn member_sources_with_ipv6_routes(
ipv6: &[&str],
ipv6_routes: &[&str],
) -> SharedVirtualNicMemberSources {
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], ipv6_routes))
}
fn member_claims(
ipv4: &[&str],
ipv6: &[&str],
@@ -2268,6 +2571,98 @@ mod tests {
assert_eq!(reply_ipv4.get_destination(), source_owner_ip);
}
#[tokio::test]
async fn dispatcher_translates_wrong_local_ipv6_source_to_destination_member() {
let source_owner = uuid::Uuid::from_u128(1);
let destination_owner = uuid::Uuid::from_u128(2);
let source_owner_ip = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
let destination_owner_ip = "2001:db8:2::1".parse::<Ipv6Addr>().unwrap();
let remote_ip = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
let (source_sender, mut source_receiver) = mpsc::channel(1);
let (destination_sender, mut destination_receiver) = mpsc::channel(1);
let mut state = SharedVirtualNicDispatcherState::default();
state.register(source_owner, member_entry(source_sender));
state.register(destination_owner, member_entry(destination_sender));
state.source_table.update_member_sources(
source_owner,
member_sources_with_ipv6(&["2001:db8:1::1/64"]),
);
state.source_table.update_member_sources(
destination_owner,
member_sources_with_ipv6_routes(&["2001:db8:2::1/64"], &["2001:db8:99::/64"]),
);
state
.forward_tun_packet_to_member(ipv6_udp_packet_with_ports(
source_owner_ip,
remote_ip,
1234,
5678,
))
.await;
assert!(source_receiver.try_recv().is_err());
let translated = destination_receiver.try_recv().unwrap();
let translated_ipv6 = pnet_packet::ipv6::Ipv6Packet::new(translated.payload()).unwrap();
assert_eq!(translated_ipv6.get_source(), destination_owner_ip);
assert_eq!(translated_ipv6.get_destination(), remote_ip);
let translated_udp =
pnet_packet::udp::UdpPacket::new(&translated.payload()[IPV6_HEADER_LEN..]).unwrap();
assert_eq!(
translated_udp.get_checksum(),
udp::ipv6_checksum(&translated_udp, &destination_owner_ip, &remote_ip,)
);
let reply = state.prepare_member_packet_to_tun(
destination_owner,
ipv6_udp_packet_with_ports(remote_ip, destination_owner_ip, 5678, 1234),
);
let reply_ipv6 = pnet_packet::ipv6::Ipv6Packet::new(reply.payload()).unwrap();
assert_eq!(reply_ipv6.get_source(), remote_ip);
assert_eq!(reply_ipv6.get_destination(), source_owner_ip);
let reply_udp =
pnet_packet::udp::UdpPacket::new(&reply.payload()[IPV6_HEADER_LEN..]).unwrap();
assert_eq!(
reply_udp.get_checksum(),
udp::ipv6_checksum(&reply_udp, &remote_ip, &source_owner_ip)
);
}
#[test]
fn rewrite_ipv6_source_updates_tcp_and_icmpv6_checksums() {
let source = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
let translated_source = "2001:db8:2::1".parse::<Ipv6Addr>().unwrap();
let destination = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
let mut tcp_packet = ipv6_tcp_packet_with_ports(source, destination, 1234, 5678);
assert!(rewrite_packet_source(
&mut tcp_packet,
SharedVirtualNicFlowAddr::from(source),
SharedVirtualNicFlowAddr::from(translated_source),
));
let tcp =
pnet_packet::tcp::TcpPacket::new(&tcp_packet.payload()[IPV6_HEADER_LEN..]).unwrap();
assert_eq!(
tcp.get_checksum(),
tcp::ipv6_checksum(&tcp, &translated_source, &destination)
);
let mut icmp_packet = ipv6_icmp_echo_packet(source, destination);
assert!(rewrite_packet_source(
&mut icmp_packet,
SharedVirtualNicFlowAddr::from(source),
SharedVirtualNicFlowAddr::from(translated_source),
));
let icmp =
pnet_packet::icmpv6::Icmpv6Packet::new(&icmp_packet.payload()[IPV6_HEADER_LEN..])
.unwrap();
assert_eq!(
icmp.get_checksum(),
pnet_packet::icmpv6::checksum(&icmp, &translated_source, &destination)
);
}
#[tokio::test]
async fn dispatcher_unregister_clears_nat_translation() {
let source_owner = uuid::Uuid::from_u128(1);
+253 -16
View File
@@ -5,6 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
happy-dom: 16.8.1
minimatch: 10.2.4
importers:
@@ -58,7 +59,7 @@ importers:
devDependencies:
'@antfu/eslint-config':
specifier: ^3.7.3
version: 3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))
version: 3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))
'@intlify/unplugin-vue-i18n':
specifier: ^5.2.0
version: 5.3.1(@vue/compiler-dom@3.5.21)(eslint@9.35.0(jiti@2.5.1))(rollup@4.50.1)(typescript@5.6.3)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
@@ -1009,8 +1010,8 @@ packages:
resolution: {integrity: sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==}
engines: {node: '>= 16'}
'@intlify/shared@11.4.8':
resolution: {integrity: sha512-XbRgrv+XEuvDr7UCY55oibVrh+o4u+A0VB6nSL0F5Z8LcZxE/8j573LYG6bCrOigIcHdGpSNI7Rh5UpC5/B/eg==}
'@intlify/shared@11.4.6':
resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==}
engines: {node: '>= 22'}
'@intlify/shared@12.0.0-alpha.4':
@@ -1412,6 +1413,9 @@ packages:
'@rushstack/ts-command-line@5.0.2':
resolution: {integrity: sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@stylistic/eslint-plugin@2.13.0':
resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1522,9 +1526,15 @@ packages:
'@types/argparse@1.0.38':
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/default-gateway@7.2.2':
resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==}
@@ -1753,6 +1763,9 @@ packages:
'@vitest/expect@2.1.9':
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
'@vitest/expect@4.1.9':
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
'@vitest/mocker@2.1.9':
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
peerDependencies:
@@ -1764,21 +1777,47 @@ packages:
vite:
optional: true
'@vitest/mocker@4.1.9':
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@2.1.9':
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
'@vitest/pretty-format@4.1.9':
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
'@vitest/runner@2.1.9':
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
'@vitest/runner@4.1.9':
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
'@vitest/snapshot@2.1.9':
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
'@vitest/snapshot@4.1.9':
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
'@vitest/spy@2.1.9':
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
'@vitest/spy@4.1.9':
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
'@vitest/utils@2.1.9':
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
'@vitest/utils@4.1.9':
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
'@volar/language-core@2.4.15':
resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==}
@@ -2284,6 +2323,10 @@ packages:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -2492,6 +2535,9 @@ packages:
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
es-module-lexer@2.1.0:
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -2793,6 +2839,15 @@ packages:
fault@2.0.1:
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -2901,7 +2956,6 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
globals@13.24.0:
@@ -3478,6 +3532,10 @@ packages:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
obug@2.1.3:
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
engines: {node: '>=12.20.0'}
onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
@@ -3583,6 +3641,10 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@@ -3875,6 +3937,9 @@ packages:
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
@@ -3973,6 +4038,14 @@ packages:
tinyexec@1.0.1:
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinypool@1.1.1:
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -3981,6 +4054,10 @@ packages:
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
engines: {node: '>=14.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tinyspy@3.0.2:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
@@ -4169,7 +4246,6 @@ packages:
unplugin-vue-router@0.10.9:
resolution: {integrity: sha512-DXmC0GMcROOnCmN56GRvi1bkkG1BnVs4xJqNvucBUeZkmB245URvtxOfbo3H6q4SOUQQbLPYWd6InzvjRh363A==}
deprecated: 'Merged into vuejs/router. Migrate: https://router.vuejs.org/guide/migration/v4-to-v5.html'
peerDependencies:
vue-router: ^4.4.0
peerDependenciesMeta:
@@ -4201,7 +4277,6 @@ packages:
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
uuid@11.1.0:
@@ -4305,7 +4380,7 @@ packages:
'@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 2.1.9
'@vitest/ui': 2.1.9
happy-dom: '*'
happy-dom: 16.8.1
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
@@ -4321,6 +4396,47 @@ packages:
jsdom:
optional: true
vitest@4.1.9:
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.9
'@vitest/browser-preview': 4.1.9
'@vitest/browser-webdriverio': 4.1.9
'@vitest/coverage-istanbul': 4.1.9
'@vitest/coverage-v8': 4.1.9
'@vitest/ui': 4.1.9
happy-dom: 16.8.1
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
@@ -4467,7 +4583,7 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@antfu/eslint-config@3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))':
'@antfu/eslint-config@3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))':
dependencies:
'@antfu/install-pkg': 1.1.0
'@clack/prompts': 0.9.1
@@ -4476,7 +4592,7 @@ snapshots:
'@stylistic/eslint-plugin': 2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
'@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
'@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
'@vitest/eslint-plugin': 1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))
'@vitest/eslint-plugin': 1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))
eslint: 9.35.0(jiti@2.5.1)
eslint-config-flat-gitignore: 1.0.1(eslint@9.35.0(jiti@2.5.1))
eslint-flat-config-utils: 1.1.0
@@ -5047,7 +5163,7 @@ snapshots:
'@intlify/shared@10.0.8': {}
'@intlify/shared@11.4.8': {}
'@intlify/shared@11.4.6': {}
'@intlify/shared@12.0.0-alpha.4': {}
@@ -5057,8 +5173,8 @@ snapshots:
dependencies:
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.5.1))
'@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))
'@intlify/shared': 11.4.8
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.8)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
'@intlify/shared': 11.4.6
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
'@typescript-eslint/scope-manager': 8.42.0
'@typescript-eslint/typescript-estree': 8.42.0(typescript@5.6.3)
@@ -5080,11 +5196,11 @@ snapshots:
- supports-color
- typescript
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.8)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))':
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))':
dependencies:
'@babel/parser': 7.28.4
optionalDependencies:
'@intlify/shared': 11.4.8
'@intlify/shared': 11.4.6
'@vue/compiler-dom': 3.5.21
vue: 3.5.21(typescript@5.6.3)
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.6.3))
@@ -5420,6 +5536,9 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
'@standard-schema/spec@1.1.0':
optional: true
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)':
dependencies:
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
@@ -5510,10 +5629,19 @@ snapshots:
'@types/argparse@1.0.38': {}
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
optional: true
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
'@types/deep-eql@4.0.2':
optional: true
'@types/default-gateway@7.2.2': {}
'@types/estree@1.0.8': {}
@@ -5711,14 +5839,14 @@ snapshots:
vite: 5.4.21(@types/node@22.18.1)
vue: 3.5.21(typescript@5.6.3)
'@vitest/eslint-plugin@1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))':
'@vitest/eslint-plugin@1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))':
dependencies:
'@typescript-eslint/scope-manager': 8.42.0
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
eslint: 9.35.0(jiti@2.5.1)
optionalDependencies:
typescript: 5.6.3
vitest: 2.1.9(@types/node@22.18.1)(happy-dom@16.8.1)
vitest: 4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1))
transitivePeerDependencies:
- supports-color
@@ -5729,6 +5857,16 @@ snapshots:
chai: 5.3.3
tinyrainbow: 1.2.0
'@vitest/expect@4.1.9':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
chai: 6.2.2
tinyrainbow: 3.1.0
optional: true
'@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.18.1))':
dependencies:
'@vitest/spy': 2.1.9
@@ -5737,31 +5875,69 @@ snapshots:
optionalDependencies:
vite: 5.4.21(@types/node@22.18.1)
'@vitest/mocker@4.1.9(vite@5.4.21(@types/node@22.18.1))':
dependencies:
'@vitest/spy': 4.1.9
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 5.4.21(@types/node@22.18.1)
optional: true
'@vitest/pretty-format@2.1.9':
dependencies:
tinyrainbow: 1.2.0
'@vitest/pretty-format@4.1.9':
dependencies:
tinyrainbow: 3.1.0
optional: true
'@vitest/runner@2.1.9':
dependencies:
'@vitest/utils': 2.1.9
pathe: 1.1.2
'@vitest/runner@4.1.9':
dependencies:
'@vitest/utils': 4.1.9
pathe: 2.0.3
optional: true
'@vitest/snapshot@2.1.9':
dependencies:
'@vitest/pretty-format': 2.1.9
magic-string: 0.30.21
pathe: 1.1.2
'@vitest/snapshot@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
'@vitest/utils': 4.1.9
magic-string: 0.30.21
pathe: 2.0.3
optional: true
'@vitest/spy@2.1.9':
dependencies:
tinyspy: 3.0.2
'@vitest/spy@4.1.9':
optional: true
'@vitest/utils@2.1.9':
dependencies:
'@vitest/pretty-format': 2.1.9
loupe: 3.2.1
tinyrainbow: 1.2.0
'@vitest/utils@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
optional: true
'@volar/language-core@2.4.15':
dependencies:
'@volar/source-map': 2.4.15
@@ -6436,6 +6612,9 @@ snapshots:
loupe: 3.2.1
pathval: 2.0.1
chai@6.2.2:
optional: true
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -6611,6 +6790,9 @@ snapshots:
es-module-lexer@1.7.0: {}
es-module-lexer@2.1.0:
optional: true
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -7059,6 +7241,11 @@ snapshots:
dependencies:
format: 0.2.2
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
optional: true
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -7850,6 +8037,9 @@ snapshots:
object-hash@3.0.0: {}
obug@2.1.3:
optional: true
onetime@6.0.0:
dependencies:
mimic-fn: 4.0.0
@@ -7954,6 +8144,9 @@ snapshots:
picomatch@4.0.3: {}
picomatch@4.0.4:
optional: true
pify@2.3.0: {}
pinia@2.3.1(typescript@5.6.3)(vue@3.5.21(typescript@5.6.3)):
@@ -8237,6 +8430,9 @@ snapshots:
std-env@3.10.0: {}
std-env@4.1.0:
optional: true
string-argv@0.3.2: {}
string-width@4.2.3:
@@ -8353,10 +8549,22 @@ snapshots:
tinyexec@1.0.1: {}
tinyexec@1.2.4:
optional: true
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
optional: true
tinypool@1.1.1: {}
tinyrainbow@1.2.0: {}
tinyrainbow@3.1.0:
optional: true
tinyspy@3.0.2: {}
to-regex-range@5.0.1:
@@ -8810,6 +9018,35 @@ snapshots:
- supports-color
- terser
vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)):
dependencies:
'@vitest/expect': 4.1.9
'@vitest/mocker': 4.1.9(vite@5.4.21(@types/node@22.18.1))
'@vitest/pretty-format': 4.1.9
'@vitest/runner': 4.1.9
'@vitest/snapshot': 4.1.9
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
es-module-lexer: 2.1.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.3
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
tinyexec: 1.2.4
tinyglobby: 0.2.17
tinyrainbow: 3.1.0
vite: 5.4.21(@types/node@22.18.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.18.1
happy-dom: 16.8.1
transitivePeerDependencies:
- msw
optional: true
vscode-uri@3.1.0: {}
vue-chartjs@5.3.2(chart.js@4.5.0)(vue@3.5.21(typescript@5.6.3)):