mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
@@ -33,5 +33,6 @@ foreign_network_whitelist = "*"
|
|||||||
disable_p2p = false
|
disable_p2p = false
|
||||||
relay_all_peer_rpc = false
|
relay_all_peer_rpc = false
|
||||||
disable_udp_hole_punching = false
|
disable_udp_hole_punching = false
|
||||||
|
disable_tcp_hole_punching = false
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ impl HealthChecker {
|
|||||||
flags.no_tun = true;
|
flags.no_tun = true;
|
||||||
flags.disable_p2p = true;
|
flags.disable_p2p = true;
|
||||||
flags.disable_udp_hole_punching = true;
|
flags.disable_udp_hole_punching = true;
|
||||||
|
flags.disable_tcp_hole_punching = true;
|
||||||
cfg.set_flags(flags);
|
cfg.set_flags(flags);
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"unplugin-vue-router": "^0.10.8",
|
"unplugin-vue-router": "^0.10.8",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.8",
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^8.0.5",
|
||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vue-i18n": "^10.0.0",
|
"vue-i18n": "^10.0.0",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ const bool_flags: BoolFlag[] = [
|
|||||||
{ field: 'multi_thread', help: 'multi_thread_help' },
|
{ field: 'multi_thread', help: 'multi_thread_help' },
|
||||||
{ field: 'proxy_forward_by_system', help: 'proxy_forward_by_system_help' },
|
{ field: 'proxy_forward_by_system', help: 'proxy_forward_by_system_help' },
|
||||||
{ field: 'disable_encryption', help: 'disable_encryption_help' },
|
{ field: 'disable_encryption', help: 'disable_encryption_help' },
|
||||||
|
{ field: 'disable_tcp_hole_punching', help: 'disable_tcp_hole_punching_help' },
|
||||||
{ field: 'disable_udp_hole_punching', help: 'disable_udp_hole_punching_help' },
|
{ field: 'disable_udp_hole_punching', help: 'disable_udp_hole_punching_help' },
|
||||||
{ field: 'disable_sym_hole_punching', help: 'disable_sym_hole_punching_help' },
|
{ field: 'disable_sym_hole_punching', help: 'disable_sym_hole_punching_help' },
|
||||||
{ field: 'enable_magic_dns', help: 'enable_magic_dns_help' },
|
{ field: 'enable_magic_dns', help: 'enable_magic_dns_help' },
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ proxy_forward_by_system_help: 通过系统内核转发子网代理数据包,
|
|||||||
disable_encryption: 禁用加密
|
disable_encryption: 禁用加密
|
||||||
disable_encryption_help: 禁用对等节点通信的加密,默认为false,必须与对等节点相同
|
disable_encryption_help: 禁用对等节点通信的加密,默认为false,必须与对等节点相同
|
||||||
|
|
||||||
|
disable_tcp_hole_punching: 禁用TCP打洞
|
||||||
|
disable_tcp_hole_punching_help: 禁用TCP打洞功能
|
||||||
|
|
||||||
disable_udp_hole_punching: 禁用UDP打洞
|
disable_udp_hole_punching: 禁用UDP打洞
|
||||||
disable_udp_hole_punching_help: 禁用UDP打洞功能
|
disable_udp_hole_punching_help: 禁用UDP打洞功能
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ proxy_forward_by_system_help: Forward packet to proxy networks via system kernel
|
|||||||
disable_encryption: Disable Encryption
|
disable_encryption: Disable Encryption
|
||||||
disable_encryption_help: Disable encryption for peers communication, default is false, must be same with peers
|
disable_encryption_help: Disable encryption for peers communication, default is false, must be same with peers
|
||||||
|
|
||||||
|
disable_tcp_hole_punching: Disable TCP Hole Punching
|
||||||
|
disable_tcp_hole_punching_help: Disable tcp hole punching
|
||||||
|
|
||||||
disable_udp_hole_punching: Disable UDP Hole Punching
|
disable_udp_hole_punching: Disable UDP Hole Punching
|
||||||
disable_udp_hole_punching_help: Disable udp hole punching
|
disable_udp_hole_punching_help: Disable udp hole punching
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export interface NetworkConfig {
|
|||||||
multi_thread?: boolean
|
multi_thread?: boolean
|
||||||
proxy_forward_by_system?: boolean
|
proxy_forward_by_system?: boolean
|
||||||
disable_encryption?: boolean
|
disable_encryption?: boolean
|
||||||
|
disable_tcp_hole_punching?: boolean
|
||||||
disable_udp_hole_punching?: boolean
|
disable_udp_hole_punching?: boolean
|
||||||
disable_sym_hole_punching?: boolean
|
disable_sym_hole_punching?: boolean
|
||||||
|
|
||||||
@@ -120,6 +121,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
|||||||
multi_thread: true,
|
multi_thread: true,
|
||||||
proxy_forward_by_system: false,
|
proxy_forward_by_system: false,
|
||||||
disable_encryption: false,
|
disable_encryption: false,
|
||||||
|
disable_tcp_hole_punching: false,
|
||||||
disable_udp_hole_punching: false,
|
disable_udp_hole_punching: false,
|
||||||
disable_sym_hole_punching: false,
|
disable_sym_hole_punching: false,
|
||||||
enable_relay_network_whitelist: false,
|
enable_relay_network_whitelist: false,
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ core_clap:
|
|||||||
p2p_only:
|
p2p_only:
|
||||||
en: "only communicate with peers that already establish p2p connection"
|
en: "only communicate with peers that already establish p2p connection"
|
||||||
zh-CN: "仅与已经建立P2P连接的对等节点通信"
|
zh-CN: "仅与已经建立P2P连接的对等节点通信"
|
||||||
|
disable_tcp_hole_punching:
|
||||||
|
en: "disable tcp hole punching"
|
||||||
|
zh-CN: "禁用TCP打洞功能"
|
||||||
disable_udp_hole_punching:
|
disable_udp_hole_punching:
|
||||||
en: "disable udp hole punching"
|
en: "disable udp hole punching"
|
||||||
zh-CN: "禁用UDP打洞功能"
|
zh-CN: "禁用UDP打洞功能"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub fn gen_default_flags() -> Flags {
|
|||||||
disable_p2p: false,
|
disable_p2p: false,
|
||||||
p2p_only: false,
|
p2p_only: false,
|
||||||
relay_all_peer_rpc: false,
|
relay_all_peer_rpc: false,
|
||||||
|
disable_tcp_hole_punching: false,
|
||||||
disable_udp_hole_punching: false,
|
disable_udp_hole_punching: false,
|
||||||
multi_thread: true,
|
multi_thread: true,
|
||||||
data_compress_algo: CompressionAlgoPb::None.into(),
|
data_compress_algo: CompressionAlgoPb::None.into(),
|
||||||
|
|||||||
+571
-42
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -9,6 +9,8 @@ use anyhow::Context;
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
|
use socket2::{SockAddr, SockRef};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::{lookup_host, UdpSocket};
|
use tokio::net::{lookup_host, UdpSocket};
|
||||||
use tokio::sync::{broadcast, Mutex};
|
use tokio::sync::{broadcast, Mutex};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
@@ -375,16 +377,28 @@ impl StunClientBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UdpNatTypeDetectResult {
|
pub enum StunTransport {
|
||||||
|
Udp,
|
||||||
|
Tcp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StunNatTypeDetectResult {
|
||||||
|
transport: StunTransport,
|
||||||
source_addr: SocketAddr,
|
source_addr: SocketAddr,
|
||||||
stun_resps: Vec<BindRequestResponse>,
|
stun_resps: Vec<BindRequestResponse>,
|
||||||
// if we are easy symmetric nat, we need to test with another port to check inc or dec
|
// if we are easy symmetric nat, we need to test with another port to check inc or dec
|
||||||
extra_bind_test: Option<BindRequestResponse>,
|
extra_bind_test: Option<BindRequestResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpNatTypeDetectResult {
|
impl StunNatTypeDetectResult {
|
||||||
fn new(source_addr: SocketAddr, stun_resps: Vec<BindRequestResponse>) -> Self {
|
fn new(
|
||||||
|
transport: StunTransport,
|
||||||
|
source_addr: SocketAddr,
|
||||||
|
stun_resps: Vec<BindRequestResponse>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
transport,
|
||||||
source_addr,
|
source_addr,
|
||||||
stun_resps,
|
stun_resps,
|
||||||
extra_bind_test: None,
|
extra_bind_test: None,
|
||||||
@@ -447,7 +461,7 @@ impl UdpNatTypeDetectResult {
|
|||||||
mapped_addr_count == 1
|
mapped_addr_count == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nat_type(&self) -> NatType {
|
fn nat_type_udp(&self) -> NatType {
|
||||||
if self.stun_server_count() < 2 {
|
if self.stun_server_count() < 2 {
|
||||||
return NatType::Unknown;
|
return NatType::Unknown;
|
||||||
}
|
}
|
||||||
@@ -498,6 +512,33 @@ impl UdpNatTypeDetectResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nat_type_tcp(&self) -> NatType {
|
||||||
|
if self.is_open_internet() {
|
||||||
|
return NatType::OpenInternet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.stun_server_count() < 2 || self.stun_resps.is_empty() {
|
||||||
|
return NatType::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_cone() {
|
||||||
|
if self.is_pat() {
|
||||||
|
NatType::NoPat
|
||||||
|
} else {
|
||||||
|
NatType::FullCone
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NatType::Symmetric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nat_type(&self) -> NatType {
|
||||||
|
match self.transport {
|
||||||
|
StunTransport::Udp => self.nat_type_udp(),
|
||||||
|
StunTransport::Tcp => self.nat_type_tcp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn public_ips(&self) -> Vec<IpAddr> {
|
pub fn public_ips(&self) -> Vec<IpAddr> {
|
||||||
self.stun_resps
|
self.stun_resps
|
||||||
.iter()
|
.iter()
|
||||||
@@ -521,7 +562,7 @@ impl UdpNatTypeDetectResult {
|
|||||||
self.source_addr
|
self.source_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend_result(&mut self, other: UdpNatTypeDetectResult) {
|
pub fn extend_result(&mut self, other: StunNatTypeDetectResult) {
|
||||||
self.stun_resps.extend(other.stun_resps);
|
self.stun_resps.extend(other.stun_resps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +616,10 @@ impl UdpNatTypeDetector {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn detect_nat_type(&self, source_port: u16) -> Result<UdpNatTypeDetectResult, Error> {
|
pub async fn detect_nat_type(
|
||||||
|
&self,
|
||||||
|
source_port: u16,
|
||||||
|
) -> Result<StunNatTypeDetectResult, Error> {
|
||||||
let udp = Arc::new(UdpSocket::bind(format!("0.0.0.0:{}", source_port)).await?);
|
let udp = Arc::new(UdpSocket::bind(format!("0.0.0.0:{}", source_port)).await?);
|
||||||
self.detect_nat_type_with_socket(udp).await
|
self.detect_nat_type_with_socket(udp).await
|
||||||
}
|
}
|
||||||
@@ -584,7 +628,7 @@ impl UdpNatTypeDetector {
|
|||||||
pub async fn detect_nat_type_with_socket(
|
pub async fn detect_nat_type_with_socket(
|
||||||
&self,
|
&self,
|
||||||
udp: Arc<UdpSocket>,
|
udp: Arc<UdpSocket>,
|
||||||
) -> Result<UdpNatTypeDetectResult, Error> {
|
) -> Result<StunNatTypeDetectResult, Error> {
|
||||||
let mut stun_servers = vec![];
|
let mut stun_servers = vec![];
|
||||||
let mut host_resolver = HostResolverIter::new(
|
let mut host_resolver = HostResolverIter::new(
|
||||||
self.stun_server_hosts.clone(),
|
self.stun_server_hosts.clone(),
|
||||||
@@ -623,7 +667,241 @@ impl UdpNatTypeDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(UdpNatTypeDetectResult::new(udp.local_addr()?, bind_resps))
|
Ok(StunNatTypeDetectResult::new(
|
||||||
|
StunTransport::Udp,
|
||||||
|
udp.local_addr()?,
|
||||||
|
bind_resps,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct TcpStunClient {
|
||||||
|
stun_server: SocketAddr,
|
||||||
|
conn_timeout: Duration,
|
||||||
|
io_timeout: Duration,
|
||||||
|
source_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpStunClient {
|
||||||
|
pub fn new(stun_server: SocketAddr, source_port: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
stun_server,
|
||||||
|
conn_timeout: Duration::from_millis(1500),
|
||||||
|
io_timeout: Duration::from_millis(3000),
|
||||||
|
source_port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_mapped_addr(msg: &Message<Attribute>) -> Option<SocketAddr> {
|
||||||
|
let mut mapped_addr = None;
|
||||||
|
for x in msg.attributes() {
|
||||||
|
match x {
|
||||||
|
Attribute::MappedAddress(addr) => {
|
||||||
|
if mapped_addr.is_none() {
|
||||||
|
let _ = mapped_addr.insert(addr.address());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attribute::XorMappedAddress(addr) => {
|
||||||
|
if mapped_addr.is_none() {
|
||||||
|
let _ = mapped_addr.insert(addr.address());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapped_addr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_size_from_header(header: &[u8; 20]) -> Result<usize, Error> {
|
||||||
|
if (header[0] & 0b1100_0000) != 0 {
|
||||||
|
return Err(Error::MessageDecodeError(
|
||||||
|
"invalid stun message type".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let msg_len = u16::from_be_bytes([header[2], header[3]]) as usize;
|
||||||
|
if !msg_len.is_multiple_of(4) {
|
||||||
|
return Err(Error::MessageDecodeError(
|
||||||
|
"invalid stun message length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let total = 20usize
|
||||||
|
.checked_add(msg_len)
|
||||||
|
.ok_or_else(|| Error::MessageDecodeError("invalid stun message size".to_string()))?;
|
||||||
|
if total > 4096 {
|
||||||
|
return Err(Error::MessageDecodeError(
|
||||||
|
"stun message too large".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tcp_read_stun_message(
|
||||||
|
stream: &mut tokio::net::TcpStream,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<Message<Attribute>, Error> {
|
||||||
|
let mut header = [0u8; 20];
|
||||||
|
tokio::time::timeout(timeout, stream.read_exact(&mut header)).await??;
|
||||||
|
let total_size = Self::message_size_from_header(&header)?;
|
||||||
|
let mut buf = vec![0u8; total_size];
|
||||||
|
buf[..20].copy_from_slice(&header);
|
||||||
|
if total_size > 20 {
|
||||||
|
tokio::time::timeout(timeout, stream.read_exact(&mut buf[20..])).await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decoder = MessageDecoder::<Attribute>::new();
|
||||||
|
let Ok(msg) = decoder
|
||||||
|
.decode_from_bytes(&buf)
|
||||||
|
.with_context(|| "decode tcp stun message")?
|
||||||
|
else {
|
||||||
|
return Err(Error::MessageDecodeError(
|
||||||
|
"invalid stun message".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect(&self) -> Result<tokio::net::TcpStream, Error> {
|
||||||
|
let bind_addr = match self.stun_server {
|
||||||
|
SocketAddr::V4(_) => {
|
||||||
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.source_port)
|
||||||
|
}
|
||||||
|
SocketAddr::V6(_) => {
|
||||||
|
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.source_port)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let socket2_socket = socket2::Socket::new(
|
||||||
|
socket2::Domain::for_address(self.stun_server),
|
||||||
|
socket2::Type::STREAM,
|
||||||
|
Some(socket2::Protocol::TCP),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if bind_addr.is_ipv6() {
|
||||||
|
socket2_socket.set_only_v6(true)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket2_socket.set_nonblocking(true)?;
|
||||||
|
socket2_socket.set_reuse_address(true)?;
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
|
||||||
|
{
|
||||||
|
let _ = socket2_socket.set_reuse_port(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket2_socket.bind(&SockAddr::from(bind_addr))?;
|
||||||
|
|
||||||
|
let socket = tokio::net::TcpSocket::from_std_stream(socket2_socket.into());
|
||||||
|
let stream =
|
||||||
|
tokio::time::timeout(self.conn_timeout, socket.connect(self.stun_server)).await??;
|
||||||
|
|
||||||
|
let _ = SockRef::from(&stream).set_linger(Some(Duration::ZERO));
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = Level::TRACE)]
|
||||||
|
pub async fn bind_request(self) -> Result<BindRequestResponse, Error> {
|
||||||
|
let mut tids = vec![];
|
||||||
|
|
||||||
|
let mut stream = self.connect().await?;
|
||||||
|
let local_addr = stream.local_addr()?;
|
||||||
|
let stun_host = self.stun_server;
|
||||||
|
|
||||||
|
let tid = rand::random::<u32>();
|
||||||
|
let message = Message::<Attribute>::new(MessageClass::Request, BINDING, u32_to_tid(tid));
|
||||||
|
let mut encoder = MessageEncoder::new();
|
||||||
|
let msg = encoder
|
||||||
|
.encode_into_bytes(message.clone())
|
||||||
|
.with_context(|| "encode tcp stun message")?;
|
||||||
|
tids.push(tid);
|
||||||
|
tokio::time::timeout(self.io_timeout, stream.write_all(msg.as_slice())).await??;
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let msg = Self::tcp_read_stun_message(&mut stream, self.io_timeout).await?;
|
||||||
|
if msg.class() != MessageClass::SuccessResponse
|
||||||
|
|| msg.method() != BINDING
|
||||||
|
|| !tids.contains(&tid_to_u32(&msg.transaction_id()))
|
||||||
|
{
|
||||||
|
return Err(Error::MessageDecodeError(
|
||||||
|
"unexpected stun response".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BindRequestResponse {
|
||||||
|
local_addr,
|
||||||
|
stun_server_addr: stun_host,
|
||||||
|
recv_from_addr: stun_host,
|
||||||
|
mapped_socket_addr: Self::extract_mapped_addr(&msg),
|
||||||
|
changed_socket_addr: None,
|
||||||
|
change_ip: false,
|
||||||
|
change_port: false,
|
||||||
|
real_ip_changed: false,
|
||||||
|
real_port_changed: false,
|
||||||
|
latency_us: now.elapsed().as_micros() as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TcpNatTypeDetector {
|
||||||
|
stun_server_hosts: Vec<String>,
|
||||||
|
max_ip_per_domain: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpNatTypeDetector {
|
||||||
|
pub fn new(stun_server_hosts: Vec<String>, max_ip_per_domain: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
stun_server_hosts,
|
||||||
|
max_ip_per_domain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub async fn detect_nat_type(
|
||||||
|
&self,
|
||||||
|
source_port: u16,
|
||||||
|
) -> Result<StunNatTypeDetectResult, Error> {
|
||||||
|
let mut stun_servers = vec![];
|
||||||
|
let mut host_resolver = HostResolverIter::new(
|
||||||
|
self.stun_server_hosts.clone(),
|
||||||
|
self.max_ip_per_domain,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
while let Some(addr) = host_resolver.next().await {
|
||||||
|
stun_servers.push(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bind_resps = vec![];
|
||||||
|
let mut source_addr = None;
|
||||||
|
let mut selected_source_port = if source_port == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(source_port)
|
||||||
|
};
|
||||||
|
for server in stun_servers.iter() {
|
||||||
|
let resp = TcpStunClient::new(*server, selected_source_port.unwrap_or(0))
|
||||||
|
.bind_request()
|
||||||
|
.await;
|
||||||
|
if let Ok(resp) = resp {
|
||||||
|
if selected_source_port.is_none() {
|
||||||
|
selected_source_port = Some(resp.local_addr.port());
|
||||||
|
}
|
||||||
|
source_addr.get_or_insert(resp.local_addr);
|
||||||
|
bind_resps.push(resp);
|
||||||
|
if bind_resps.len() >= 3 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(source_addr) = source_addr else {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
};
|
||||||
|
Ok(StunNatTypeDetectResult::new(
|
||||||
|
StunTransport::Tcp,
|
||||||
|
source_addr,
|
||||||
|
bind_resps,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,12 +910,15 @@ impl UdpNatTypeDetector {
|
|||||||
pub trait StunInfoCollectorTrait: Send + Sync {
|
pub trait StunInfoCollectorTrait: Send + Sync {
|
||||||
fn get_stun_info(&self) -> StunInfo;
|
fn get_stun_info(&self) -> StunInfo;
|
||||||
async fn get_udp_port_mapping(&self, local_port: u16) -> Result<SocketAddr, Error>;
|
async fn get_udp_port_mapping(&self, local_port: u16) -> Result<SocketAddr, Error>;
|
||||||
|
async fn get_tcp_port_mapping(&self, local_port: u16) -> Result<SocketAddr, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StunInfoCollector {
|
pub struct StunInfoCollector {
|
||||||
stun_servers: Arc<RwLock<Vec<String>>>,
|
stun_servers: Arc<RwLock<Vec<String>>>,
|
||||||
|
tcp_stun_servers: Arc<RwLock<Vec<String>>>,
|
||||||
stun_servers_v6: Arc<RwLock<Vec<String>>>,
|
stun_servers_v6: Arc<RwLock<Vec<String>>>,
|
||||||
udp_nat_test_result: Arc<RwLock<Option<UdpNatTypeDetectResult>>>,
|
udp_nat_test_result: Arc<RwLock<Option<StunNatTypeDetectResult>>>,
|
||||||
|
tcp_nat_test_result: Arc<RwLock<Option<StunNatTypeDetectResult>>>,
|
||||||
public_ipv6: Arc<AtomicCell<Option<Ipv6Addr>>>,
|
public_ipv6: Arc<AtomicCell<Option<Ipv6Addr>>>,
|
||||||
nat_test_result_time: Arc<AtomicCell<chrono::DateTime<Local>>>,
|
nat_test_result_time: Arc<AtomicCell<chrono::DateTime<Local>>>,
|
||||||
redetect_notify: Arc<tokio::sync::Notify>,
|
redetect_notify: Arc<tokio::sync::Notify>,
|
||||||
@@ -650,21 +931,44 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
|||||||
fn get_stun_info(&self) -> StunInfo {
|
fn get_stun_info(&self) -> StunInfo {
|
||||||
self.start_stun_routine();
|
self.start_stun_routine();
|
||||||
|
|
||||||
let Some(result) = self.udp_nat_test_result.read().unwrap().clone() else {
|
let udp_result = self.udp_nat_test_result.read().unwrap().clone();
|
||||||
|
let tcp_result = self.tcp_nat_test_result.read().unwrap().clone();
|
||||||
|
if udp_result.is_none() && tcp_result.is_none() {
|
||||||
return Default::default();
|
return Default::default();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
let mut public_ip = BTreeSet::<String>::new();
|
||||||
|
if let Some(result) = &udp_result {
|
||||||
|
public_ip.extend(result.public_ips().into_iter().map(|x| x.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(result) = &tcp_result {
|
||||||
|
public_ip.extend(result.public_ips().into_iter().map(|x| x.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(v6) = self.public_ipv6.load() {
|
||||||
|
public_ip.insert(v6.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
StunInfo {
|
StunInfo {
|
||||||
udp_nat_type: result.nat_type() as i32,
|
udp_nat_type: udp_result
|
||||||
tcp_nat_type: 0,
|
.as_ref()
|
||||||
|
.map(|x| x.nat_type() as i32)
|
||||||
|
.unwrap_or(NatType::Unknown as i32),
|
||||||
|
tcp_nat_type: tcp_result
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.nat_type() as i32)
|
||||||
|
.unwrap_or(NatType::Unknown as i32),
|
||||||
last_update_time: self.nat_test_result_time.load().timestamp(),
|
last_update_time: self.nat_test_result_time.load().timestamp(),
|
||||||
public_ip: result
|
public_ip: public_ip.into_iter().collect(),
|
||||||
.public_ips()
|
min_port: udp_result
|
||||||
.iter()
|
.as_ref()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.min_port() as u32)
|
||||||
.chain(self.public_ipv6.load().map(|x| x.to_string()))
|
.or_else(|| tcp_result.as_ref().map(|x| x.min_port() as u32))
|
||||||
.collect(),
|
.unwrap_or(0),
|
||||||
min_port: result.min_port() as u32,
|
max_port: udp_result
|
||||||
max_port: result.max_port() as u32,
|
.as_ref()
|
||||||
|
.map(|x| x.max_port() as u32)
|
||||||
|
.or_else(|| tcp_result.as_ref().map(|x| x.max_port() as u32))
|
||||||
|
.unwrap_or(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -715,14 +1019,60 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
|||||||
|
|
||||||
Err(Error::NotFound)
|
Err(Error::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_tcp_port_mapping(&self, local_port: u16) -> Result<SocketAddr, Error> {
|
||||||
|
self.start_stun_routine();
|
||||||
|
|
||||||
|
let mut stun_servers = self
|
||||||
|
.tcp_nat_test_result
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
.map(|x| x.collect_available_stun_server())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if stun_servers.is_empty() {
|
||||||
|
let mut host_resolver =
|
||||||
|
HostResolverIter::new(self.tcp_stun_servers.read().unwrap().clone(), 2, false);
|
||||||
|
while let Some(addr) = host_resolver.next().await {
|
||||||
|
stun_servers.push(addr);
|
||||||
|
if stun_servers.len() >= 2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stun_servers.is_empty() {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
for server in stun_servers.iter() {
|
||||||
|
let Ok(ret) = TcpStunClient::new(*server, local_port).bind_request().await else {
|
||||||
|
tracing::warn!(?server, "tcp stun bind request failed");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mapped_addr) = ret.mapped_socket_addr {
|
||||||
|
return Ok(mapped_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::NotFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StunInfoCollector {
|
impl StunInfoCollector {
|
||||||
pub fn new(stun_servers: Vec<String>, stun_servers_v6: Vec<String>) -> Self {
|
pub fn new(
|
||||||
|
udp_stun_servers: Vec<String>,
|
||||||
|
tcp_stun_servers: Vec<String>,
|
||||||
|
stun_servers_v6: Vec<String>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stun_servers: Arc::new(RwLock::new(stun_servers)),
|
stun_servers: Arc::new(RwLock::new(udp_stun_servers)),
|
||||||
|
tcp_stun_servers: Arc::new(RwLock::new(tcp_stun_servers)),
|
||||||
stun_servers_v6: Arc::new(RwLock::new(stun_servers_v6)),
|
stun_servers_v6: Arc::new(RwLock::new(stun_servers_v6)),
|
||||||
udp_nat_test_result: Arc::new(RwLock::new(None)),
|
udp_nat_test_result: Arc::new(RwLock::new(None)),
|
||||||
|
tcp_nat_test_result: Arc::new(RwLock::new(None)),
|
||||||
public_ipv6: Arc::new(AtomicCell::new(None)),
|
public_ipv6: Arc::new(AtomicCell::new(None)),
|
||||||
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
|
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
|
||||||
redetect_notify: Arc::new(tokio::sync::Notify::new()),
|
redetect_notify: Arc::new(tokio::sync::Notify::new()),
|
||||||
@@ -732,7 +1082,11 @@ impl StunInfoCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_default_servers() -> Self {
|
pub fn new_with_default_servers() -> Self {
|
||||||
Self::new(Self::get_default_servers(), Self::get_default_servers_v6())
|
Self::new(
|
||||||
|
Self::get_default_servers(),
|
||||||
|
Self::get_default_tcp_servers(),
|
||||||
|
Self::get_default_servers_v6(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stun_servers(&self, stun_servers: Vec<String>) {
|
pub fn set_stun_servers(&self, stun_servers: Vec<String>) {
|
||||||
@@ -745,6 +1099,11 @@ impl StunInfoCollector {
|
|||||||
*g = stun_servers_v6;
|
*g = stun_servers_v6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tcp_stun_servers(&self, stun_servers: Vec<String>) {
|
||||||
|
let mut g = self.tcp_stun_servers.write().unwrap();
|
||||||
|
*g = stun_servers;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_default_servers() -> Vec<String> {
|
pub fn get_default_servers() -> Vec<String> {
|
||||||
// NOTICE: we may need to choose stun server based on geolocation
|
// NOTICE: we may need to choose stun server based on geolocation
|
||||||
// stun server cross nation may return an external ip address with high latency and loss rate
|
// stun server cross nation may return an external ip address with high latency and loss rate
|
||||||
@@ -759,6 +1118,21 @@ impl StunInfoCollector {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_default_tcp_servers() -> Vec<String> {
|
||||||
|
[
|
||||||
|
"stun.hot-chilli.net",
|
||||||
|
"stun.fitauto.ru",
|
||||||
|
"fwa.lifesizecloud.com",
|
||||||
|
"global.turn.twilio.com",
|
||||||
|
"turn.cloudflare.com",
|
||||||
|
"stun.voip.blackberry.com",
|
||||||
|
"stun.radiojar.com",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_default_servers_v6() -> Vec<String> {
|
pub fn get_default_servers_v6() -> Vec<String> {
|
||||||
["txt:stun-v6.easytier.cn"]
|
["txt:stun-v6.easytier.cn"]
|
||||||
.iter()
|
.iter()
|
||||||
@@ -794,35 +1168,35 @@ impl StunInfoCollector {
|
|||||||
|
|
||||||
let stun_servers = self.stun_servers.clone();
|
let stun_servers = self.stun_servers.clone();
|
||||||
let udp_nat_test_result = self.udp_nat_test_result.clone();
|
let udp_nat_test_result = self.udp_nat_test_result.clone();
|
||||||
let udp_test_time = self.nat_test_result_time.clone();
|
let nat_test_time = self.nat_test_result_time.clone();
|
||||||
let redetect_notify = self.redetect_notify.clone();
|
let redetect_notify = self.redetect_notify.clone();
|
||||||
self.tasks.lock().unwrap().spawn(async move {
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let servers = stun_servers.read().unwrap().clone();
|
let udp_servers = stun_servers.read().unwrap().clone();
|
||||||
// use first three and random choose one from the rest
|
let udp_servers: Vec<String> = udp_servers
|
||||||
let servers = servers
|
|
||||||
.iter()
|
.iter()
|
||||||
.take(2)
|
.take(2)
|
||||||
.chain(servers.iter().skip(2).choose(&mut rand::thread_rng()))
|
.chain(udp_servers.iter().skip(2).choose(&mut rand::thread_rng()))
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
let detector = UdpNatTypeDetector::new(servers, 1);
|
|
||||||
let mut ret = detector.detect_nat_type(0).await;
|
let udp_detector = UdpNatTypeDetector::new(udp_servers, 1);
|
||||||
tracing::debug!(?ret, "finish udp nat type detect");
|
let mut udp_ret = udp_detector.detect_nat_type(0).await;
|
||||||
|
tracing::debug!(?udp_ret, "finish udp nat type detect");
|
||||||
|
|
||||||
let mut nat_type = NatType::Unknown;
|
let mut nat_type = NatType::Unknown;
|
||||||
if let Ok(resp) = &ret {
|
if let Ok(resp) = &udp_ret {
|
||||||
tracing::debug!(?resp, "got udp nat type detect result");
|
tracing::debug!(?resp, "got udp nat type detect result");
|
||||||
nat_type = resp.nat_type();
|
nat_type = resp.nat_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if nat type is symmtric, detect with another port to gather more info
|
// if nat type is symmtric, detect with another port to gather more info
|
||||||
if nat_type == NatType::Symmetric {
|
if nat_type == NatType::Symmetric {
|
||||||
let old_resp = ret.as_mut().unwrap();
|
let old_resp = udp_ret.as_mut().unwrap();
|
||||||
tracing::debug!(?old_resp, "start get extra bind result");
|
tracing::debug!(?old_resp, "start get extra bind result");
|
||||||
let available_stun_servers = old_resp.collect_available_stun_server();
|
let available_stun_servers = old_resp.collect_available_stun_server();
|
||||||
for server in available_stun_servers.iter() {
|
for server in available_stun_servers.iter() {
|
||||||
let ret = detector
|
let ret = udp_detector
|
||||||
.get_extra_bind_result(0, *server)
|
.get_extra_bind_result(0, *server)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "get extra bind result failed");
|
.with_context(|| "get extra bind result failed");
|
||||||
@@ -835,8 +1209,8 @@ impl StunInfoCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut sleep_sec = 10;
|
let mut sleep_sec = 10;
|
||||||
if let Ok(resp) = &ret {
|
if let Ok(resp) = &udp_ret {
|
||||||
udp_test_time.store(Local::now());
|
nat_test_time.store(Local::now());
|
||||||
*udp_nat_test_result.write().unwrap() = Some(resp.clone());
|
*udp_nat_test_result.write().unwrap() = Some(resp.clone());
|
||||||
if nat_type != NatType::Unknown
|
if nat_type != NatType::Unknown
|
||||||
&& (nat_type != NatType::Symmetric || resp.extra_bind_test.is_some())
|
&& (nat_type != NatType::Symmetric || resp.extra_bind_test.is_some())
|
||||||
@@ -852,6 +1226,40 @@ impl StunInfoCollector {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let tcp_stun_servers = self.tcp_stun_servers.clone();
|
||||||
|
let tcp_nat_test_result = self.tcp_nat_test_result.clone();
|
||||||
|
let nat_test_time = self.nat_test_result_time.clone();
|
||||||
|
let redetect_notify = self.redetect_notify.clone();
|
||||||
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
|
loop {
|
||||||
|
let tcp_servers = tcp_stun_servers.read().unwrap().clone();
|
||||||
|
let tcp_servers: Vec<String> = tcp_servers
|
||||||
|
.iter()
|
||||||
|
.take(2)
|
||||||
|
.chain(tcp_servers.iter().skip(2).choose(&mut rand::thread_rng()))
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let tcp_detector = TcpNatTypeDetector::new(tcp_servers, 1);
|
||||||
|
let tcp_ret = tcp_detector.detect_nat_type(0).await;
|
||||||
|
tracing::debug!(?tcp_ret, "finish tcp nat type detect");
|
||||||
|
|
||||||
|
let mut sleep_sec = 10;
|
||||||
|
if let Ok(resp) = &tcp_ret {
|
||||||
|
nat_test_time.store(Local::now());
|
||||||
|
*tcp_nat_test_result.write().unwrap() = Some(resp.clone());
|
||||||
|
if resp.nat_type() != NatType::Unknown {
|
||||||
|
sleep_sec = 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = redetect_notify.notified() => {}
|
||||||
|
_ = tokio::time::sleep(Duration::from_secs(sleep_sec)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// for ipv6
|
// for ipv6
|
||||||
let stun_servers = self.stun_servers_v6.clone();
|
let stun_servers = self.stun_servers_v6.clone();
|
||||||
let stored_ipv6 = self.public_ipv6.clone();
|
let stored_ipv6 = self.public_ipv6.clone();
|
||||||
@@ -878,7 +1286,7 @@ impl StunInfoCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_stun_info(&self) {
|
pub fn update_stun_info(&self) {
|
||||||
self.redetect_notify.notify_one();
|
self.redetect_notify.notify_waiters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,6 +1313,13 @@ impl StunInfoCollectorTrait for MockStunInfoCollector {
|
|||||||
}
|
}
|
||||||
Ok(format!("127.0.0.1:{}", port).parse().unwrap())
|
Ok(format!("127.0.0.1:{}", port).parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_tcp_port_mapping(&self, mut port: u16) -> Result<std::net::SocketAddr, Error> {
|
||||||
|
if port == 0 {
|
||||||
|
port = 40144;
|
||||||
|
}
|
||||||
|
Ok(format!("127.0.0.1:{}", port).parse().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -962,9 +1377,9 @@ mod tests {
|
|||||||
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let ret = detector.detect_nat_type(0).await;
|
let ret = detector.detect_nat_type(0).await;
|
||||||
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
println!("{:#?}, {:?}", ret, ret.as_ref().map(|x| x.nat_type()));
|
||||||
if ret.is_ok() {
|
if let Ok(resp) = ret {
|
||||||
assert!(!ret.unwrap().stun_resps.is_empty());
|
assert!(!resp.stun_resps.is_empty());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,6 +1389,120 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn test_public_tcp_stun_server_fitauto_ru() {
|
||||||
|
let stun_servers = vec![
|
||||||
|
"stun.fitauto.ru".to_string(),
|
||||||
|
"stun.hot-chilli.net".to_string(),
|
||||||
|
];
|
||||||
|
let detector = TcpNatTypeDetector::new(stun_servers, 3);
|
||||||
|
let ret = detector.detect_nat_type(0).await;
|
||||||
|
println!("{:#?}, {:?}", ret, ret.as_ref().map(|x| x.nat_type()));
|
||||||
|
if let Ok(resp) = ret {
|
||||||
|
assert!(!resp.stun_resps.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_internal_tcp_stun_server_reuse_same_local_port() {
|
||||||
|
use stun_codec::rfc5389::attributes::XorMappedAddress;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
async fn spawn_tcp_stun_server() -> SocketAddr {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let server_addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let (mut stream, peer_addr) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
|
let req = TcpStunClient::tcp_read_stun_message(&mut stream, Duration::from_secs(2))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut resp_msg = Message::<Attribute>::new(
|
||||||
|
MessageClass::SuccessResponse,
|
||||||
|
BINDING,
|
||||||
|
req.transaction_id(),
|
||||||
|
);
|
||||||
|
resp_msg.add_attribute(Attribute::XorMappedAddress(XorMappedAddress::new(
|
||||||
|
peer_addr,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut encoder = MessageEncoder::new();
|
||||||
|
let rsp_buf = encoder.encode_into_bytes(resp_msg).unwrap();
|
||||||
|
stream.write_all(rsp_buf.as_slice()).await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
server_addr
|
||||||
|
}
|
||||||
|
|
||||||
|
let server1 = spawn_tcp_stun_server().await;
|
||||||
|
let server2 = spawn_tcp_stun_server().await;
|
||||||
|
|
||||||
|
let stun_servers = vec![server1.to_string(), server2.to_string()];
|
||||||
|
let detector = TcpNatTypeDetector::new(stun_servers, 1);
|
||||||
|
|
||||||
|
let ret = detector.detect_nat_type(0).await.unwrap();
|
||||||
|
assert!(ret.stun_resps.len() >= 2);
|
||||||
|
|
||||||
|
let local_ports = ret
|
||||||
|
.stun_resps
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.local_addr.port())
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
assert_eq!(local_ports.len(), 1);
|
||||||
|
|
||||||
|
let mapped_ports = ret
|
||||||
|
.stun_resps
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.mapped_socket_addr.unwrap().port())
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
assert_eq!(mapped_ports.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
local_ports.into_iter().next(),
|
||||||
|
mapped_ports.into_iter().next()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_stun_info_collector_tcp_port_mapping() {
|
||||||
|
use stun_codec::rfc5389::attributes::XorMappedAddress;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let server_addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
for _ in 0..8 {
|
||||||
|
let Ok((mut stream, peer_addr)) = listener.accept().await else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = TcpStunClient::tcp_read_stun_message(&mut stream, Duration::from_secs(2))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut resp_msg = Message::<Attribute>::new(
|
||||||
|
MessageClass::SuccessResponse,
|
||||||
|
BINDING,
|
||||||
|
req.transaction_id(),
|
||||||
|
);
|
||||||
|
resp_msg.add_attribute(Attribute::XorMappedAddress(XorMappedAddress::new(
|
||||||
|
peer_addr,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut encoder = MessageEncoder::new();
|
||||||
|
let rsp_buf = encoder.encode_into_bytes(resp_msg).unwrap();
|
||||||
|
stream.write_all(rsp_buf.as_slice()).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let collector = StunInfoCollector::new(vec![], vec![server_addr.to_string()], vec![]);
|
||||||
|
collector.set_tcp_stun_servers(vec![server_addr.to_string()]);
|
||||||
|
let mapped = collector.get_tcp_port_mapping(0).await.unwrap();
|
||||||
|
assert_eq!(mapped.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||||
|
assert!(mapped.port() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_v4_stun() {
|
async fn test_v4_stun() {
|
||||||
let mut udp_server = UdpTunnelListener::new("udp://0.0.0.0:55355".parse().unwrap());
|
let mut udp_server = UdpTunnelListener::new("udp://0.0.0.0:55355".parse().unwrap());
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ use crate::{
|
|||||||
use_global_var,
|
use_global_var,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::proto::api::instance::PeerConnInfo;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::{net::UdpSocket, task::JoinSet, time::timeout};
|
use tokio::{net::UdpSocket, task::JoinSet, time::timeout};
|
||||||
@@ -51,7 +50,6 @@ static TESTING: AtomicBool = AtomicBool::new(false);
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait PeerManagerForDirectConnector {
|
pub trait PeerManagerForDirectConnector {
|
||||||
async fn list_peers(&self) -> Vec<PeerId>;
|
async fn list_peers(&self) -> Vec<PeerId>;
|
||||||
async fn list_peer_conns(&self, peer_id: PeerId) -> Option<Vec<PeerConnInfo>>;
|
|
||||||
fn get_peer_rpc_mgr(&self) -> Arc<PeerRpcManager>;
|
fn get_peer_rpc_mgr(&self) -> Arc<PeerRpcManager>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,10 +71,6 @@ impl PeerManagerForDirectConnector for PeerManager {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_peer_conns(&self, peer_id: PeerId) -> Option<Vec<PeerConnInfo>> {
|
|
||||||
self.get_peer_map().list_peer_conns(peer_id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_peer_rpc_mgr(&self) -> Arc<PeerRpcManager> {
|
fn get_peer_rpc_mgr(&self) -> Arc<PeerRpcManager> {
|
||||||
self.get_peer_rpc_mgr()
|
self.get_peer_rpc_mgr()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use crate::{
|
|||||||
|
|
||||||
pub mod direct;
|
pub mod direct;
|
||||||
pub mod manual;
|
pub mod manual;
|
||||||
|
pub mod tcp_hole_punch;
|
||||||
pub mod udp_hole_punch;
|
pub mod udp_hole_punch;
|
||||||
|
|
||||||
pub mod dns_connector;
|
pub mod dns_connector;
|
||||||
|
|||||||
@@ -0,0 +1,730 @@
|
|||||||
|
use std::{
|
||||||
|
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Error};
|
||||||
|
use rand::Rng as _;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::{join_joinset_background, stun::StunInfoCollectorTrait, PeerId},
|
||||||
|
connector::udp_hole_punch::BackOff,
|
||||||
|
peers::{
|
||||||
|
peer_manager::PeerManager,
|
||||||
|
peer_task::{PeerTaskLauncher, PeerTaskManager},
|
||||||
|
},
|
||||||
|
proto::{
|
||||||
|
common::NatType,
|
||||||
|
peer_rpc::{
|
||||||
|
TcpHolePunchRequest, TcpHolePunchResponse, TcpHolePunchRpc,
|
||||||
|
TcpHolePunchRpcClientFactory, TcpHolePunchRpcServer,
|
||||||
|
},
|
||||||
|
rpc_types::{self, controller::BaseController},
|
||||||
|
},
|
||||||
|
tunnel::{
|
||||||
|
common::setup_sokcet2,
|
||||||
|
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||||
|
TunnelConnector as _, TunnelListener as _,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BLACKLIST_TIMEOUT_SEC: u64 = 3600;
|
||||||
|
|
||||||
|
fn handle_rpc_result<T>(
|
||||||
|
ret: Result<T, rpc_types::error::Error>,
|
||||||
|
dst_peer_id: PeerId,
|
||||||
|
blacklist: &timedmap::TimedMap<PeerId, ()>,
|
||||||
|
) -> Result<T, rpc_types::error::Error> {
|
||||||
|
match ret {
|
||||||
|
Ok(ret) => Ok(ret),
|
||||||
|
Err(e) => {
|
||||||
|
if matches!(e, rpc_types::error::Error::InvalidServiceKey(_, _)) {
|
||||||
|
blacklist.insert(dst_peer_id, (), Duration::from_secs(BLACKLIST_TIMEOUT_SEC));
|
||||||
|
}
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_symmetric_tcp_nat(nat_type: NatType) -> bool {
|
||||||
|
matches!(
|
||||||
|
nat_type,
|
||||||
|
NatType::Symmetric | NatType::SymmetricEasyInc | NatType::SymmetricEasyDec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_addr_for_port(port: u16, is_v6: bool) -> SocketAddr {
|
||||||
|
if is_v6 {
|
||||||
|
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
||||||
|
} else {
|
||||||
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn select_local_port(peer_mgr: &Arc<PeerManager>, is_v6: bool) -> Result<u16, Error> {
|
||||||
|
let bind_addr = bind_addr_for_port(0, is_v6);
|
||||||
|
tracing::trace!(?bind_addr, is_v6, "tcp hole punch select local port");
|
||||||
|
let _g = peer_mgr.get_global_ctx().net_ns.guard();
|
||||||
|
let listener = tokio::net::TcpListener::bind(bind_addr).await?;
|
||||||
|
let port = listener.local_addr()?.port();
|
||||||
|
tracing::debug!(?bind_addr, port, "tcp hole punch selected local port");
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_syn_from_port(
|
||||||
|
peer_mgr: &Arc<PeerManager>,
|
||||||
|
local_port: u16,
|
||||||
|
dst: SocketAddr,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bind_addr = bind_addr_for_port(local_port, dst.is_ipv6());
|
||||||
|
tracing::debug!(?bind_addr, ?dst, "tcp hole punch send syn");
|
||||||
|
let _g = peer_mgr.get_global_ctx().net_ns.guard();
|
||||||
|
|
||||||
|
let socket2_socket = socket2::Socket::new(
|
||||||
|
socket2::Domain::for_address(dst),
|
||||||
|
socket2::Type::STREAM,
|
||||||
|
Some(socket2::Protocol::TCP),
|
||||||
|
)?;
|
||||||
|
setup_sokcet2(&socket2_socket, &bind_addr)?;
|
||||||
|
let socket = tokio::net::TcpSocket::from_std_stream(socket2_socket.into());
|
||||||
|
match tokio::time::timeout(Duration::from_millis(600), socket.connect(dst)).await {
|
||||||
|
Ok(Ok(_stream)) => {
|
||||||
|
tracing::trace!(?bind_addr, ?dst, "tcp hole punch syn connect succeeded");
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
tracing::trace!(?bind_addr, ?dst, ?e, "tcp hole punch syn connect failed");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::trace!(?bind_addr, ?dst, ?e, "tcp hole punch syn connect timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcp support simultaneous connect, so initiator and server can both use connect.
|
||||||
|
async fn try_connect_to_remote(
|
||||||
|
peer_mgr: Arc<PeerManager>,
|
||||||
|
a_mapped_addr: SocketAddr,
|
||||||
|
local_port: u16,
|
||||||
|
is_client: bool,
|
||||||
|
max_attempts: u32,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
tracing::info!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
"tcp hole punch server start connect loop"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut connector =
|
||||||
|
TcpTunnelConnector::new(format!("tcp://{}", a_mapped_addr).parse().unwrap());
|
||||||
|
connector.set_bind_addrs(vec![bind_addr_for_port(
|
||||||
|
local_port,
|
||||||
|
a_mapped_addr.is_ipv6(),
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let start = tokio::time::Instant::now();
|
||||||
|
let mut attempts: u32 = 0;
|
||||||
|
while start.elapsed() < Duration::from_secs(10) && attempts < max_attempts {
|
||||||
|
attempts = attempts.wrapping_add(1);
|
||||||
|
let _g = peer_mgr.get_global_ctx().net_ns.guard();
|
||||||
|
if let Ok(Ok(tunnel)) =
|
||||||
|
tokio::time::timeout(Duration::from_secs(3), connector.connect()).await
|
||||||
|
{
|
||||||
|
let add_tunnel_ret = if is_client {
|
||||||
|
peer_mgr.add_client_tunnel(tunnel, false).await.map(|_| ())
|
||||||
|
} else {
|
||||||
|
peer_mgr.add_tunnel_as_server(tunnel, false).await
|
||||||
|
};
|
||||||
|
if let Err(e) = add_tunnel_ret {
|
||||||
|
tracing::error!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
attempts,
|
||||||
|
?e,
|
||||||
|
"tcp hole punch server connected and added client tunnel failed"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
tracing::info!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
attempts,
|
||||||
|
is_client,
|
||||||
|
"tcp hole punch server connected and added tunnel"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracing::trace!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
attempts,
|
||||||
|
"tcp hole punch server connect attempt failed"
|
||||||
|
);
|
||||||
|
let sleep_ms = rand::thread_rng().gen_range(10..100);
|
||||||
|
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::warn!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
attempts,
|
||||||
|
"tcp hole punch server connect loop timeout"
|
||||||
|
);
|
||||||
|
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"tcp hole punch server connect loop timeout"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TcpHolePunchServer {
|
||||||
|
peer_mgr: Arc<PeerManager>,
|
||||||
|
tasks: Arc<std::sync::Mutex<JoinSet<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpHolePunchServer {
|
||||||
|
fn new(peer_mgr: Arc<PeerManager>) -> Arc<Self> {
|
||||||
|
let tasks = Arc::new(std::sync::Mutex::new(JoinSet::new()));
|
||||||
|
join_joinset_background(tasks.clone(), "tcp hole punch server".to_string());
|
||||||
|
Arc::new(Self { peer_mgr, tasks })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl TcpHolePunchRpc for TcpHolePunchServer {
|
||||||
|
type Controller = BaseController;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self), fields(a_mapped_addr = ?input.connector_mapped_addr), err)]
|
||||||
|
async fn exchange_mapped_addr(
|
||||||
|
&self,
|
||||||
|
_ctrl: Self::Controller,
|
||||||
|
input: TcpHolePunchRequest,
|
||||||
|
) -> rpc_types::error::Result<TcpHolePunchResponse> {
|
||||||
|
let my_tcp_nat_type = NatType::try_from(
|
||||||
|
self.peer_mgr
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_stun_info_collector()
|
||||||
|
.get_stun_info()
|
||||||
|
.tcp_nat_type,
|
||||||
|
)
|
||||||
|
.unwrap_or(NatType::Unknown);
|
||||||
|
tracing::debug!(?my_tcp_nat_type, "tcp hole punch rpc received");
|
||||||
|
if matches!(my_tcp_nat_type, NatType::Unknown) {
|
||||||
|
tracing::warn!(?my_tcp_nat_type, "tcp hole punch rpc rejected (unknown)");
|
||||||
|
return Err(anyhow::anyhow!("tcp nat type unknown not supported").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let a_mapped_addr = input
|
||||||
|
.connector_mapped_addr
|
||||||
|
.ok_or(anyhow::anyhow!("connector_mapped_addr is required"))?;
|
||||||
|
let a_mapped_addr: SocketAddr = a_mapped_addr.into();
|
||||||
|
let a_ip = a_mapped_addr.ip();
|
||||||
|
if a_ip.is_unspecified() || a_ip.is_multicast() {
|
||||||
|
tracing::warn!(?a_mapped_addr, "tcp hole punch rpc invalid connector addr");
|
||||||
|
return Err(anyhow::anyhow!("connector_mapped_addr is malformed").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_v6 = a_mapped_addr.is_ipv6();
|
||||||
|
let local_port = select_local_port(&self.peer_mgr, is_v6).await?;
|
||||||
|
let mapped_addr = self
|
||||||
|
.peer_mgr
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_stun_info_collector()
|
||||||
|
.get_tcp_port_mapping(local_port)
|
||||||
|
.await
|
||||||
|
.with_context(|| "failed to get tcp port mapping")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?a_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
?mapped_addr,
|
||||||
|
"tcp hole punch rpc responding with listener mapped addr and start connecting"
|
||||||
|
);
|
||||||
|
|
||||||
|
let peer_mgr = self.peer_mgr.clone();
|
||||||
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
|
let _ = try_connect_to_remote(peer_mgr, a_mapped_addr, local_port, true, 5).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(TcpHolePunchResponse {
|
||||||
|
listener_mapped_addr: Some(mapped_addr.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TcpHolePunchConnectorData {
|
||||||
|
peer_mgr: Arc<PeerManager>,
|
||||||
|
blacklist: Arc<timedmap::TimedMap<PeerId, ()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpHolePunchConnectorData {
|
||||||
|
fn new(peer_mgr: Arc<PeerManager>) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
peer_mgr,
|
||||||
|
blacklist: Arc::new(timedmap::TimedMap::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn punch_as_initiator(self: Arc<Self>, dst_peer_id: PeerId) -> Result<(), Error> {
|
||||||
|
let mut backoff = BackOff::new(vec![1000, 1000, 4000, 8000]);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
backoff.sleep_for_next_backoff().await;
|
||||||
|
if self.do_punch_as_initiator(dst_peer_id).await.is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.blacklist.contains(&dst_peer_id) {
|
||||||
|
tracing::warn!(
|
||||||
|
dst_peer_id,
|
||||||
|
"tcp hole punch initiator skipped (blacklisted)"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self), fields(dst_peer_id), err)]
|
||||||
|
async fn do_punch_as_initiator(&self, dst_peer_id: PeerId) -> Result<(), Error> {
|
||||||
|
let global_ctx = self.peer_mgr.get_global_ctx();
|
||||||
|
let my_tcp_nat_type = NatType::try_from(
|
||||||
|
global_ctx
|
||||||
|
.get_stun_info_collector()
|
||||||
|
.get_stun_info()
|
||||||
|
.tcp_nat_type,
|
||||||
|
)
|
||||||
|
.unwrap_or(NatType::Unknown);
|
||||||
|
tracing::debug!(?my_tcp_nat_type, "tcp hole punch initiator start");
|
||||||
|
if is_symmetric_tcp_nat(my_tcp_nat_type) || my_tcp_nat_type == NatType::Unknown {
|
||||||
|
tracing::debug!("tcp hole punch initiator skipped (symmetric)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_port = select_local_port(&self.peer_mgr, false).await?;
|
||||||
|
let mapped_addr = global_ctx
|
||||||
|
.get_stun_info_collector()
|
||||||
|
.get_tcp_port_mapping(local_port)
|
||||||
|
.await
|
||||||
|
.with_context(|| "failed to get tcp port mapping")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
local_port,
|
||||||
|
?mapped_addr,
|
||||||
|
"tcp hole punch initiator got mapped addr, start rpc exchange"
|
||||||
|
);
|
||||||
|
|
||||||
|
let rpc_stub = self
|
||||||
|
.peer_mgr
|
||||||
|
.get_peer_rpc_mgr()
|
||||||
|
.rpc_client()
|
||||||
|
.scoped_client::<TcpHolePunchRpcClientFactory<BaseController>>(
|
||||||
|
self.peer_mgr.my_peer_id(),
|
||||||
|
dst_peer_id,
|
||||||
|
global_ctx.get_network_name(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = rpc_stub
|
||||||
|
.exchange_mapped_addr(
|
||||||
|
BaseController {
|
||||||
|
timeout_ms: 6000,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TcpHolePunchRequest {
|
||||||
|
connector_mapped_addr: Some(mapped_addr.into()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let resp = handle_rpc_result(resp, dst_peer_id, &self.blacklist)?;
|
||||||
|
let remote_mapped_addr = resp
|
||||||
|
.listener_mapped_addr
|
||||||
|
.ok_or(anyhow::anyhow!("listener_mapped_addr is required"))?;
|
||||||
|
let remote_mapped_addr: SocketAddr = remote_mapped_addr.into();
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
?remote_mapped_addr,
|
||||||
|
"tcp hole punch initiator rpc returned"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(()) = try_connect_to_remote(
|
||||||
|
self.peer_mgr.clone(),
|
||||||
|
remote_mapped_addr,
|
||||||
|
local_port,
|
||||||
|
false,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
local_port,
|
||||||
|
?remote_mapped_addr,
|
||||||
|
"tcp hole punch initiator connected to remote mapped addr with simultaneous connection"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
dst_peer_id,
|
||||||
|
local_port,
|
||||||
|
?remote_mapped_addr,
|
||||||
|
"tcp hole punch initiator sent syn to remote mapped addr"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut listener =
|
||||||
|
TcpTunnelListener::new(format!("tcp://0.0.0.0:{}", local_port).parse().unwrap());
|
||||||
|
{
|
||||||
|
let _g = self.peer_mgr.get_global_ctx().net_ns.guard();
|
||||||
|
listener.listen().await?;
|
||||||
|
}
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
local_port,
|
||||||
|
url = %listener.local_url(),
|
||||||
|
"tcp hole punch initiator listening"
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::time::timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
self.accept_loop(&mut listener, dst_peer_id),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
"tcp hole punch initiator accepted and added server tunnel"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn accept_loop(
|
||||||
|
&self,
|
||||||
|
listener: &mut TcpTunnelListener,
|
||||||
|
dst_peer_id: PeerId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
loop {
|
||||||
|
match listener.accept().await {
|
||||||
|
Ok(tunnel) => {
|
||||||
|
if let Err(e) = self.peer_mgr.add_tunnel_as_server(tunnel, false).await {
|
||||||
|
tracing::error!("tcp hole punch add tunnel error: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
dst_peer_id,
|
||||||
|
"tcp hole punch initiator accepted and added server tunnel"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("tcp hole punch accept error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
struct TcpPunchTaskInfo {
|
||||||
|
dst_peer_id: PeerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TcpHolePunchPeerTaskLauncher {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl PeerTaskLauncher for TcpHolePunchPeerTaskLauncher {
|
||||||
|
type Data = Arc<TcpHolePunchConnectorData>;
|
||||||
|
type CollectPeerItem = TcpPunchTaskInfo;
|
||||||
|
type TaskRet = ();
|
||||||
|
|
||||||
|
fn new_data(&self, peer_mgr: Arc<PeerManager>) -> Self::Data {
|
||||||
|
TcpHolePunchConnectorData::new(peer_mgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self, data))]
|
||||||
|
async fn collect_peers_need_task(&self, data: &Self::Data) -> Vec<Self::CollectPeerItem> {
|
||||||
|
let global_ctx = data.peer_mgr.get_global_ctx();
|
||||||
|
let my_tcp_nat_type = NatType::try_from(
|
||||||
|
global_ctx
|
||||||
|
.get_stun_info_collector()
|
||||||
|
.get_stun_info()
|
||||||
|
.tcp_nat_type,
|
||||||
|
)
|
||||||
|
.unwrap_or(NatType::Unknown);
|
||||||
|
if is_symmetric_tcp_nat(my_tcp_nat_type) || my_tcp_nat_type == NatType::Unknown {
|
||||||
|
tracing::trace!(
|
||||||
|
?my_tcp_nat_type,
|
||||||
|
"tcp hole punch task collect skipped (symmetric)"
|
||||||
|
);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let my_peer_id = data.peer_mgr.my_peer_id();
|
||||||
|
|
||||||
|
data.blacklist.cleanup();
|
||||||
|
|
||||||
|
let mut peers_to_connect = Vec::new();
|
||||||
|
for route in data.peer_mgr.list_routes().await.iter() {
|
||||||
|
if route
|
||||||
|
.feature_flag
|
||||||
|
.map(|x| x.is_public_server)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let peer_id: PeerId = route.peer_id;
|
||||||
|
if peer_id == my_peer_id {
|
||||||
|
tracing::trace!(peer_id, "tcp hole punch task collect skip self");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.blacklist.contains(&peer_id) {
|
||||||
|
tracing::debug!(peer_id, "tcp hole punch task collect skip blacklisted");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.peer_mgr.get_peer_map().has_peer(peer_id) {
|
||||||
|
tracing::trace!(peer_id, "tcp hole punch task collect skip already has peer");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let peer_tcp_nat_type = route
|
||||||
|
.stun_info
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.tcp_nat_type)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let peer_tcp_nat_type =
|
||||||
|
NatType::try_from(peer_tcp_nat_type).unwrap_or(NatType::Unknown);
|
||||||
|
if matches!(peer_tcp_nat_type, NatType::Unknown) {
|
||||||
|
tracing::debug!(
|
||||||
|
peer_id,
|
||||||
|
?peer_tcp_nat_type,
|
||||||
|
"tcp hole punch task collect skip peer unknown"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
peer_id,
|
||||||
|
my_peer_id,
|
||||||
|
?my_tcp_nat_type,
|
||||||
|
?peer_tcp_nat_type,
|
||||||
|
"tcp hole punch task collect add peer"
|
||||||
|
);
|
||||||
|
peers_to_connect.push(TcpPunchTaskInfo {
|
||||||
|
dst_peer_id: peer_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
peers_to_connect
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn launch_task(
|
||||||
|
&self,
|
||||||
|
data: &Self::Data,
|
||||||
|
item: Self::CollectPeerItem,
|
||||||
|
) -> tokio::task::JoinHandle<Result<Self::TaskRet, anyhow::Error>> {
|
||||||
|
let data = data.clone();
|
||||||
|
tokio::spawn(async move { data.punch_as_initiator(item.dst_peer_id).await.map(|_| ()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn all_task_done(&self, _data: &Self::Data) {}
|
||||||
|
|
||||||
|
fn loop_interval_ms(&self) -> u64 {
|
||||||
|
5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TcpHolePunchConnector {
|
||||||
|
server: Arc<TcpHolePunchServer>,
|
||||||
|
client: PeerTaskManager<TcpHolePunchPeerTaskLauncher>,
|
||||||
|
peer_mgr: Arc<PeerManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpHolePunchConnector {
|
||||||
|
pub fn new(peer_mgr: Arc<PeerManager>) -> Self {
|
||||||
|
Self {
|
||||||
|
server: TcpHolePunchServer::new(peer_mgr.clone()),
|
||||||
|
client: PeerTaskManager::new(TcpHolePunchPeerTaskLauncher {}, peer_mgr.clone()),
|
||||||
|
peer_mgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_as_client(&mut self) -> Result<(), Error> {
|
||||||
|
tracing::info!("tcp hole punch client start");
|
||||||
|
self.client.start();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_as_server(&mut self) -> Result<(), Error> {
|
||||||
|
tracing::info!("tcp hole punch server register rpc");
|
||||||
|
self.peer_mgr
|
||||||
|
.get_peer_rpc_mgr()
|
||||||
|
.rpc_server()
|
||||||
|
.registry()
|
||||||
|
.register(
|
||||||
|
TcpHolePunchRpcServer::new(self.server.clone()),
|
||||||
|
&self.peer_mgr.get_global_ctx().get_network_name(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<(), Error> {
|
||||||
|
if self.peer_mgr.get_global_ctx().get_flags().disable_p2p {
|
||||||
|
tracing::debug!("tcp hole punch disabled by disable_p2p");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run_as_client().await?;
|
||||||
|
self.run_as_server().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::{error::Error, stun::StunInfoCollectorTrait},
|
||||||
|
connector::tcp_hole_punch::TcpHolePunchConnector,
|
||||||
|
peers::{
|
||||||
|
peer_manager::PeerManager,
|
||||||
|
tests::{connect_peer_manager, create_mock_peer_manager, wait_route_appear},
|
||||||
|
},
|
||||||
|
proto::common::{NatType, StunInfo},
|
||||||
|
tunnel::common::tests::wait_for_condition,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MockStunInfoCollector {
|
||||||
|
udp_nat_type: NatType,
|
||||||
|
tcp_nat_type: NatType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl StunInfoCollectorTrait for MockStunInfoCollector {
|
||||||
|
fn get_stun_info(&self) -> StunInfo {
|
||||||
|
StunInfo {
|
||||||
|
udp_nat_type: self.udp_nat_type as i32,
|
||||||
|
tcp_nat_type: self.tcp_nat_type as i32,
|
||||||
|
last_update_time: 0,
|
||||||
|
public_ip: vec!["127.0.0.1".to_string(), "::1".to_string()],
|
||||||
|
min_port: 100,
|
||||||
|
max_port: 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_udp_port_mapping(&self, mut port: u16) -> Result<SocketAddr, Error> {
|
||||||
|
if port == 0 {
|
||||||
|
port = 40144;
|
||||||
|
}
|
||||||
|
Ok(format!("127.0.0.1:{}", port).parse().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_tcp_port_mapping(&self, mut port: u16) -> Result<SocketAddr, Error> {
|
||||||
|
if port == 0 {
|
||||||
|
port = 40144;
|
||||||
|
}
|
||||||
|
Ok(format!("127.0.0.1:{}", port).parse().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_stun_info_collector(peer_mgr: Arc<PeerManager>, tcp_nat_type: NatType) {
|
||||||
|
let collector = Box::new(MockStunInfoCollector {
|
||||||
|
udp_nat_type: NatType::Unknown,
|
||||||
|
tcp_nat_type,
|
||||||
|
});
|
||||||
|
peer_mgr
|
||||||
|
.get_global_ctx()
|
||||||
|
.replace_stun_info_collector(collector);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tcp_hole_punch_connects() {
|
||||||
|
let p_a = create_mock_peer_manager().await;
|
||||||
|
let p_b = create_mock_peer_manager().await;
|
||||||
|
let p_c = create_mock_peer_manager().await;
|
||||||
|
|
||||||
|
replace_stun_info_collector(p_a.clone(), NatType::PortRestricted);
|
||||||
|
replace_stun_info_collector(p_b.clone(), NatType::PortRestricted);
|
||||||
|
replace_stun_info_collector(p_c.clone(), NatType::PortRestricted);
|
||||||
|
|
||||||
|
connect_peer_manager(p_a.clone(), p_b.clone()).await;
|
||||||
|
connect_peer_manager(p_b.clone(), p_c.clone()).await;
|
||||||
|
wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap();
|
||||||
|
|
||||||
|
let mut hole_punching_a = TcpHolePunchConnector::new(p_a.clone());
|
||||||
|
let mut hole_punching_c = TcpHolePunchConnector::new(p_c.clone());
|
||||||
|
hole_punching_a.run().await.unwrap();
|
||||||
|
hole_punching_c.run().await.unwrap();
|
||||||
|
|
||||||
|
hole_punching_a.client.run_immediately().await;
|
||||||
|
hole_punching_c.client.run_immediately().await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| {
|
||||||
|
let p_a = p_a.clone();
|
||||||
|
let p_c = p_c.clone();
|
||||||
|
async move {
|
||||||
|
let a_has = p_a
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(p_c.my_peer_id())
|
||||||
|
.await
|
||||||
|
.is_some_and(|c| !c.is_empty());
|
||||||
|
let c_has = p_c
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(p_a.my_peer_id())
|
||||||
|
.await
|
||||||
|
.is_some_and(|c| !c.is_empty());
|
||||||
|
a_has || c_has
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(15),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn tcp_hole_punch_skip_symmetric_peer() {
|
||||||
|
let p_a = create_mock_peer_manager().await;
|
||||||
|
let p_b = create_mock_peer_manager().await;
|
||||||
|
let p_c = create_mock_peer_manager().await;
|
||||||
|
|
||||||
|
replace_stun_info_collector(p_a.clone(), NatType::Symmetric);
|
||||||
|
replace_stun_info_collector(p_b.clone(), NatType::PortRestricted);
|
||||||
|
replace_stun_info_collector(p_c.clone(), NatType::Symmetric);
|
||||||
|
|
||||||
|
connect_peer_manager(p_a.clone(), p_b.clone()).await;
|
||||||
|
connect_peer_manager(p_b.clone(), p_c.clone()).await;
|
||||||
|
wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap();
|
||||||
|
|
||||||
|
let mut hole_punching_a = TcpHolePunchConnector::new(p_a.clone());
|
||||||
|
let mut hole_punching_c = TcpHolePunchConnector::new(p_c.clone());
|
||||||
|
hole_punching_a.run().await.unwrap();
|
||||||
|
hole_punching_c.run().await.unwrap();
|
||||||
|
|
||||||
|
hole_punching_a.client.run_immediately().await;
|
||||||
|
hole_punching_c.client.run_immediately().await;
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
assert!(p_a
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(p_c.my_peer_id())
|
||||||
|
.await
|
||||||
|
.map(|c| c.is_empty())
|
||||||
|
.unwrap_or(true));
|
||||||
|
assert!(p_c
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(p_a.my_peer_id())
|
||||||
|
.await
|
||||||
|
.map(|c| c.is_empty())
|
||||||
|
.unwrap_or(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ use tokio::{sync::Mutex, task::JoinHandle};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{stun::StunInfoCollectorTrait, PeerId},
|
common::{stun::StunInfoCollectorTrait, PeerId},
|
||||||
connector::direct::PeerManagerForDirectConnector,
|
|
||||||
peers::{
|
peers::{
|
||||||
peer_manager::PeerManager,
|
peer_manager::PeerManager,
|
||||||
peer_task::{PeerTaskLauncher, PeerTaskManager},
|
peer_task::{PeerTaskLauncher, PeerTaskManager},
|
||||||
@@ -461,8 +460,7 @@ impl PeerTaskLauncher for UdpHolePunchPeerTaskLauncher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let conns = data.peer_mgr.list_peer_conns(peer_id).await;
|
if data.peer_mgr.get_peer_map().has_peer(peer_id) {
|
||||||
if conns.is_some() && !conns.unwrap().is_empty() {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -421,6 +421,15 @@ struct NetworkOptions {
|
|||||||
)]
|
)]
|
||||||
disable_udp_hole_punching: Option<bool>,
|
disable_udp_hole_punching: Option<bool>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
env = "ET_DISABLE_TCP_HOLE_PUNCHING",
|
||||||
|
help = t!("core_clap.disable_tcp_hole_punching").to_string(),
|
||||||
|
num_args = 0..=1,
|
||||||
|
default_missing_value = "true"
|
||||||
|
)]
|
||||||
|
disable_tcp_hole_punching: Option<bool>,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
env = "ET_DISABLE_SYM_HOLE_PUNCHING",
|
env = "ET_DISABLE_SYM_HOLE_PUNCHING",
|
||||||
@@ -925,6 +934,9 @@ impl NetworkOptions {
|
|||||||
}
|
}
|
||||||
f.disable_p2p = self.disable_p2p.unwrap_or(f.disable_p2p);
|
f.disable_p2p = self.disable_p2p.unwrap_or(f.disable_p2p);
|
||||||
f.p2p_only = self.p2p_only.unwrap_or(f.p2p_only);
|
f.p2p_only = self.p2p_only.unwrap_or(f.p2p_only);
|
||||||
|
f.disable_tcp_hole_punching = self
|
||||||
|
.disable_tcp_hole_punching
|
||||||
|
.unwrap_or(f.disable_tcp_hole_punching);
|
||||||
f.disable_udp_hole_punching = self
|
f.disable_udp_hole_punching = self
|
||||||
.disable_udp_hole_punching
|
.disable_udp_hole_punching
|
||||||
.unwrap_or(f.disable_udp_hole_punching);
|
.unwrap_or(f.disable_udp_hole_punching);
|
||||||
|
|||||||
@@ -1470,7 +1470,9 @@ async fn main() -> Result<(), Error> {
|
|||||||
let collector = StunInfoCollector::new_with_default_servers();
|
let collector = StunInfoCollector::new_with_default_servers();
|
||||||
loop {
|
loop {
|
||||||
let ret = collector.get_stun_info();
|
let ret = collector.get_stun_info();
|
||||||
if ret.udp_nat_type != NatType::Unknown as i32 {
|
if ret.udp_nat_type != NatType::Unknown as i32
|
||||||
|
&& ret.tcp_nat_type != NatType::Unknown as i32
|
||||||
|
{
|
||||||
if cli.output_format == OutputFormat::Json {
|
if cli.output_format == OutputFormat::Json {
|
||||||
match serde_json::to_string_pretty(&ret) {
|
match serde_json::to_string_pretty(&ret) {
|
||||||
Ok(json) => println!("{}", json),
|
Ok(json) => println!("{}", json),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use crate::common::scoped_task::ScopedTask;
|
|||||||
use crate::common::PeerId;
|
use crate::common::PeerId;
|
||||||
use crate::connector::direct::DirectConnectorManager;
|
use crate::connector::direct::DirectConnectorManager;
|
||||||
use crate::connector::manual::{ConnectorManagerRpcService, ManualConnectorManager};
|
use crate::connector::manual::{ConnectorManagerRpcService, ManualConnectorManager};
|
||||||
|
use crate::connector::tcp_hole_punch::TcpHolePunchConnector;
|
||||||
use crate::connector::udp_hole_punch::UdpHolePunchConnector;
|
use crate::connector::udp_hole_punch::UdpHolePunchConnector;
|
||||||
use crate::gateway::icmp_proxy::IcmpProxy;
|
use crate::gateway::icmp_proxy::IcmpProxy;
|
||||||
use crate::gateway::kcp_proxy::{KcpProxyDst, KcpProxyDstRpcService, KcpProxySrc};
|
use crate::gateway::kcp_proxy::{KcpProxyDst, KcpProxyDstRpcService, KcpProxySrc};
|
||||||
@@ -516,6 +517,7 @@ pub struct Instance {
|
|||||||
conn_manager: Arc<ManualConnectorManager>,
|
conn_manager: Arc<ManualConnectorManager>,
|
||||||
direct_conn_manager: Arc<DirectConnectorManager>,
|
direct_conn_manager: Arc<DirectConnectorManager>,
|
||||||
udp_hole_puncher: Arc<Mutex<UdpHolePunchConnector>>,
|
udp_hole_puncher: Arc<Mutex<UdpHolePunchConnector>>,
|
||||||
|
tcp_hole_puncher: Arc<Mutex<TcpHolePunchConnector>>,
|
||||||
|
|
||||||
ip_proxy: Option<IpProxy>,
|
ip_proxy: Option<IpProxy>,
|
||||||
|
|
||||||
@@ -571,6 +573,7 @@ impl Instance {
|
|||||||
direct_conn_manager.run();
|
direct_conn_manager.run();
|
||||||
|
|
||||||
let udp_hole_puncher = UdpHolePunchConnector::new(peer_manager.clone());
|
let udp_hole_puncher = UdpHolePunchConnector::new(peer_manager.clone());
|
||||||
|
let tcp_hole_puncher = TcpHolePunchConnector::new(peer_manager.clone());
|
||||||
|
|
||||||
let peer_center = Arc::new(PeerCenterInstance::new(peer_manager.clone()));
|
let peer_center = Arc::new(PeerCenterInstance::new(peer_manager.clone()));
|
||||||
|
|
||||||
@@ -594,6 +597,7 @@ impl Instance {
|
|||||||
conn_manager,
|
conn_manager,
|
||||||
direct_conn_manager: Arc::new(direct_conn_manager),
|
direct_conn_manager: Arc::new(direct_conn_manager),
|
||||||
udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)),
|
udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)),
|
||||||
|
tcp_hole_puncher: Arc::new(Mutex::new(tcp_hole_puncher)),
|
||||||
|
|
||||||
ip_proxy: None,
|
ip_proxy: None,
|
||||||
kcp_proxy_src: None,
|
kcp_proxy_src: None,
|
||||||
@@ -949,6 +953,7 @@ impl Instance {
|
|||||||
self.run_ip_proxy().await?;
|
self.run_ip_proxy().await?;
|
||||||
|
|
||||||
self.udp_hole_puncher.lock().await.run().await?;
|
self.udp_hole_puncher.lock().await.run().await?;
|
||||||
|
self.tcp_hole_puncher.lock().await.run().await?;
|
||||||
|
|
||||||
self.peer_center.init().await;
|
self.peer_center.init().await;
|
||||||
let route_calc = self.peer_center.get_cost_calculator();
|
let route_calc = self.peer_center.get_cost_calculator();
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -740,6 +740,10 @@ impl NetworkConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(disable_tcp_hole_punching) = self.disable_tcp_hole_punching {
|
||||||
|
flags.disable_tcp_hole_punching = disable_tcp_hole_punching;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(disable_udp_hole_punching) = self.disable_udp_hole_punching {
|
if let Some(disable_udp_hole_punching) = self.disable_udp_hole_punching {
|
||||||
flags.disable_udp_hole_punching = disable_udp_hole_punching;
|
flags.disable_udp_hole_punching = disable_udp_hole_punching;
|
||||||
}
|
}
|
||||||
@@ -898,6 +902,7 @@ impl NetworkConfig {
|
|||||||
result.multi_thread = Some(flags.multi_thread);
|
result.multi_thread = Some(flags.multi_thread);
|
||||||
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
||||||
result.disable_encryption = Some(!flags.enable_encryption);
|
result.disable_encryption = Some(!flags.enable_encryption);
|
||||||
|
result.disable_tcp_hole_punching = Some(flags.disable_tcp_hole_punching);
|
||||||
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
||||||
result.disable_sym_hole_punching = Some(flags.disable_sym_hole_punching);
|
result.disable_sym_hole_punching = Some(flags.disable_sym_hole_punching);
|
||||||
result.enable_magic_dns = Some(flags.accept_dns);
|
result.enable_magic_dns = Some(flags.accept_dns);
|
||||||
@@ -1140,6 +1145,7 @@ mod tests {
|
|||||||
flags.multi_thread = rng.gen_bool(0.7);
|
flags.multi_thread = rng.gen_bool(0.7);
|
||||||
flags.proxy_forward_by_system = rng.gen_bool(0.3);
|
flags.proxy_forward_by_system = rng.gen_bool(0.3);
|
||||||
flags.enable_encryption = rng.gen_bool(0.8);
|
flags.enable_encryption = rng.gen_bool(0.8);
|
||||||
|
flags.disable_tcp_hole_punching = rng.gen_bool(0.2);
|
||||||
flags.disable_udp_hole_punching = rng.gen_bool(0.2);
|
flags.disable_udp_hole_punching = rng.gen_bool(0.2);
|
||||||
flags.accept_dns = rng.gen_bool(0.6);
|
flags.accept_dns = rng.gen_bool(0.6);
|
||||||
flags.mtu = rng.gen_range(1200..1500);
|
flags.mtu = rng.gen_range(1200..1500);
|
||||||
|
|||||||
@@ -545,11 +545,12 @@ mod tests {
|
|||||||
|
|
||||||
println!("rpc service ready, {:#?}", rpc_service.global_peer_map);
|
println!("rpc service ready, {:#?}", rpc_service.global_peer_map);
|
||||||
|
|
||||||
if digest.is_none() {
|
if let Some(prev) = digest {
|
||||||
digest = Some(rpc_service.global_peer_map_digest.load());
|
|
||||||
} else {
|
|
||||||
let v = rpc_service.global_peer_map_digest.load();
|
let v = rpc_service.global_peer_map_digest.load();
|
||||||
assert_eq!(digest.unwrap(), v);
|
assert_eq!(prev, v);
|
||||||
|
digest = Some(prev);
|
||||||
|
} else {
|
||||||
|
digest = Some(rpc_service.global_peer_map_digest.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut route_cost = pc.get_cost_calculator();
|
let mut route_cost = pc.get_cost_calculator();
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ impl RoutePeerInfo {
|
|||||||
ipv4_addr: None,
|
ipv4_addr: None,
|
||||||
proxy_cidrs: Vec::new(),
|
proxy_cidrs: Vec::new(),
|
||||||
hostname: None,
|
hostname: None,
|
||||||
udp_stun_info: 0,
|
udp_nat_type: 0,
|
||||||
|
tcp_nat_type: 0,
|
||||||
// ensure this is updated when the peer_infos/conn_info/foreign_network lock is acquired.
|
// ensure this is updated when the peer_infos/conn_info/foreign_network lock is acquired.
|
||||||
// else we may assign a older timestamp than iterate time.
|
// else we may assign a older timestamp than iterate time.
|
||||||
last_update: None,
|
last_update: None,
|
||||||
@@ -160,6 +161,7 @@ impl RoutePeerInfo {
|
|||||||
peer_route_id: u64,
|
peer_route_id: u64,
|
||||||
global_ctx: &ArcGlobalCtx,
|
global_ctx: &ArcGlobalCtx,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let stun_info = global_ctx.get_stun_info_collector().get_stun_info();
|
||||||
Self {
|
Self {
|
||||||
peer_id: my_peer_id,
|
peer_id: my_peer_id,
|
||||||
inst_id: Some(global_ctx.get_id().into()),
|
inst_id: Some(global_ctx.get_id().into()),
|
||||||
@@ -174,10 +176,8 @@ impl RoutePeerInfo {
|
|||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
hostname: Some(global_ctx.get_hostname()),
|
hostname: Some(global_ctx.get_hostname()),
|
||||||
udp_stun_info: global_ctx
|
udp_nat_type: stun_info.udp_nat_type,
|
||||||
.get_stun_info_collector()
|
tcp_nat_type: stun_info.tcp_nat_type,
|
||||||
.get_stun_info()
|
|
||||||
.udp_nat_type,
|
|
||||||
|
|
||||||
// these two fields should not participate in comparison.
|
// these two fields should not participate in comparison.
|
||||||
last_update: None,
|
last_update: None,
|
||||||
@@ -251,9 +251,12 @@ impl From<RoutePeerInfo> for crate::proto::api::instance::Route {
|
|||||||
hostname: val.hostname.unwrap_or_default(),
|
hostname: val.hostname.unwrap_or_default(),
|
||||||
stun_info: {
|
stun_info: {
|
||||||
let mut stun_info = StunInfo::default();
|
let mut stun_info = StunInfo::default();
|
||||||
if let Ok(udp_nat_type) = NatType::try_from(val.udp_stun_info) {
|
if let Ok(udp_nat_type) = NatType::try_from(val.udp_nat_type) {
|
||||||
stun_info.set_udp_nat_type(udp_nat_type);
|
stun_info.set_udp_nat_type(udp_nat_type);
|
||||||
}
|
}
|
||||||
|
if let Ok(tcp_nat_type) = NatType::try_from(val.tcp_nat_type) {
|
||||||
|
stun_info.set_tcp_nat_type(tcp_nat_type);
|
||||||
|
}
|
||||||
Some(stun_info)
|
Some(stun_info)
|
||||||
},
|
},
|
||||||
inst_id: val.inst_id.map(|x| x.to_string()).unwrap_or_default(),
|
inst_id: val.inst_id.map(|x| x.to_string()).unwrap_or_default(),
|
||||||
@@ -869,10 +872,10 @@ impl RouteTable {
|
|||||||
self.get_next_hop(peer_id).is_some()
|
self.get_next_hop(peer_id).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nat_type(&self, peer_id: PeerId) -> Option<NatType> {
|
fn get_udp_nat_type(&self, peer_id: PeerId) -> Option<NatType> {
|
||||||
self.peer_infos
|
self.peer_infos
|
||||||
.get(&peer_id)
|
.get(&peer_id)
|
||||||
.map(|x| NatType::try_from(x.udp_stun_info).unwrap_or_default())
|
.map(|x| NatType::try_from(x.udp_nat_type).unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
// return graph and start node index (node of my peer id).
|
// return graph and start node index (node of my peer id).
|
||||||
@@ -2516,7 +2519,7 @@ impl RouteSessionManager {
|
|||||||
let mut new_initiator_dst = None;
|
let mut new_initiator_dst = None;
|
||||||
// if any peer has NoPAT or OpenInternet stun type, we should use it.
|
// if any peer has NoPAT or OpenInternet stun type, we should use it.
|
||||||
for peer_id in initiator_candidates.iter() {
|
for peer_id in initiator_candidates.iter() {
|
||||||
let Some(nat_type) = service_impl.route_table.get_nat_type(*peer_id) else {
|
let Some(nat_type) = service_impl.route_table.get_udp_nat_type(*peer_id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if nat_type == NatType::NoPat || nat_type == NatType::OpenInternet {
|
if nat_type == NatType::NoPat || nat_type == NatType::OpenInternet {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ message NetworkConfig {
|
|||||||
optional bool p2p_only = 51;
|
optional bool p2p_only = 51;
|
||||||
optional common.CompressionAlgoPb data_compress_algo = 52;
|
optional common.CompressionAlgoPb data_compress_algo = 52;
|
||||||
optional string encryption_algorithm = 53;
|
optional string encryption_algorithm = 53;
|
||||||
|
optional bool disable_tcp_hole_punching = 54;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PortForwardConfig {
|
message PortForwardConfig {
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ message FlagsInConfig {
|
|||||||
string tld_dns_zone = 31;
|
string tld_dns_zone = 31;
|
||||||
|
|
||||||
bool p2p_only = 32;
|
bool p2p_only = 32;
|
||||||
|
|
||||||
|
bool disable_tcp_hole_punching = 34;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcDescriptor {
|
message RpcDescriptor {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ message RoutePeerInfo {
|
|||||||
optional common.Ipv4Addr ipv4_addr = 4;
|
optional common.Ipv4Addr ipv4_addr = 4;
|
||||||
repeated string proxy_cidrs = 5;
|
repeated string proxy_cidrs = 5;
|
||||||
optional string hostname = 6;
|
optional string hostname = 6;
|
||||||
common.NatType udp_stun_info = 7;
|
common.NatType udp_nat_type = 7;
|
||||||
google.protobuf.Timestamp last_update = 8;
|
google.protobuf.Timestamp last_update = 8;
|
||||||
uint32 version = 9;
|
uint32 version = 9;
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ message RoutePeerInfo {
|
|||||||
optional common.Ipv6Inet ipv6_addr = 15;
|
optional common.Ipv6Inet ipv6_addr = 15;
|
||||||
|
|
||||||
repeated PeerGroupInfo groups = 16;
|
repeated PeerGroupInfo groups = 16;
|
||||||
|
|
||||||
|
common.NatType tcp_nat_type = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PeerIdVersion {
|
message PeerIdVersion {
|
||||||
@@ -207,6 +209,14 @@ service UdpHolePunchRpc {
|
|||||||
returns (SendPunchPacketBothEasySymResponse);
|
returns (SendPunchPacketBothEasySymResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message TcpHolePunchRequest { common.SocketAddr connector_mapped_addr = 1; }
|
||||||
|
|
||||||
|
message TcpHolePunchResponse { common.SocketAddr listener_mapped_addr = 1; }
|
||||||
|
|
||||||
|
service TcpHolePunchRpc {
|
||||||
|
rpc ExchangeMappedAddr(TcpHolePunchRequest) returns (TcpHolePunchResponse);
|
||||||
|
}
|
||||||
|
|
||||||
message DirectConnectedPeerInfo { int32 latency_ms = 1; }
|
message DirectConnectedPeerInfo { int32 latency_ms = 1; }
|
||||||
|
|
||||||
message PeerInfoForGlobalMap {
|
message PeerInfoForGlobalMap {
|
||||||
|
|||||||
@@ -134,25 +134,25 @@ impl FakeTcpTunnelListener {
|
|||||||
IpAddr::V6(ip) => (None, Some(ip)),
|
IpAddr::V6(ip) => (None, Some(ip)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ret = self
|
let ret = match self.stack_map.entry(interface_name.to_string()) {
|
||||||
.stack_map
|
dashmap::Entry::Occupied(entry) => entry.get().clone(),
|
||||||
.entry(interface_name.to_string())
|
dashmap::Entry::Vacant(entry) => {
|
||||||
.or_insert_with(|| {
|
let tun = create_tun(interface_name, None, local_socket_addr)?;
|
||||||
let tun = create_tun(interface_name, None, local_socket_addr);
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
?local_socket_addr,
|
?local_socket_addr,
|
||||||
"create new stack with interface_name: {:?}",
|
"create new stack with interface_name: {:?}",
|
||||||
interface_name
|
interface_name
|
||||||
);
|
);
|
||||||
// TODO: Get local MAC address of the interface
|
let stack = Arc::new(Mutex::new(stack::Stack::new(
|
||||||
Arc::new(Mutex::new(stack::Stack::new(
|
|
||||||
tun,
|
tun,
|
||||||
local_ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
|
local_ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
|
||||||
local_ip6,
|
local_ip6,
|
||||||
accept_result.mac,
|
accept_result.mac,
|
||||||
)))
|
)));
|
||||||
})
|
entry.insert(stack.clone());
|
||||||
.clone();
|
stack
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ impl crate::tunnel::TunnelConnector for FakeTcpTunnelConnector {
|
|||||||
IpAddr::V6(ip) => (None, Some(ip)),
|
IpAddr::V6(ip) => (None, Some(ip)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tun = create_tun(&interface_name, Some(remote_addr), local_addr);
|
let tun = create_tun(&interface_name, Some(remote_addr), local_addr)?;
|
||||||
let local_ip = local_ip.unwrap_or("0.0.0.0".parse().unwrap());
|
let local_ip = local_ip.unwrap_or("0.0.0.0".parse().unwrap());
|
||||||
let mut stack = stack::Stack::new(tun, local_ip, local_ip6, mac);
|
let mut stack = stack::Stack::new(tun, local_ip, local_ip6, mac);
|
||||||
let driver_type = stack.driver_type();
|
let driver_type = stack.driver_type();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pub mod pnet;
|
pub mod pnet;
|
||||||
|
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{io, net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(target_os = "linux")] {
|
if #[cfg(target_os = "linux")] {
|
||||||
@@ -10,19 +10,19 @@ cfg_if::cfg_if! {
|
|||||||
interface_name: &str,
|
interface_name: &str,
|
||||||
src_addr: Option<SocketAddr>,
|
src_addr: Option<SocketAddr>,
|
||||||
dst_addr: SocketAddr,
|
dst_addr: SocketAddr,
|
||||||
) -> Arc<dyn super::stack::Tun> {
|
) -> io::Result<Arc<dyn super::stack::Tun>> {
|
||||||
match linux_bpf::LinuxBpfTun::new(interface_name, src_addr, dst_addr) {
|
match linux_bpf::LinuxBpfTun::new(interface_name, src_addr, dst_addr) {
|
||||||
Ok(tun) => Arc::new(tun),
|
Ok(tun) => Ok(Arc::new(tun)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
?e,
|
?e,
|
||||||
interface_name,
|
interface_name,
|
||||||
"LinuxBpfTun init failed, falling back to PnetTun"
|
"LinuxBpfTun init failed, falling back to PnetTun"
|
||||||
);
|
);
|
||||||
Arc::new(pnet::PnetTun::new(
|
Ok(Arc::new(pnet::PnetTun::new(
|
||||||
interface_name,
|
interface_name,
|
||||||
pnet::create_packet_filter(src_addr, dst_addr),
|
pnet::create_packet_filter(src_addr, dst_addr),
|
||||||
))
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,19 +33,19 @@ cfg_if::cfg_if! {
|
|||||||
interface_name: &str,
|
interface_name: &str,
|
||||||
src_addr: Option<SocketAddr>,
|
src_addr: Option<SocketAddr>,
|
||||||
dst_addr: SocketAddr,
|
dst_addr: SocketAddr,
|
||||||
) -> Arc<dyn super::stack::Tun> {
|
) -> io::Result<Arc<dyn super::stack::Tun>> {
|
||||||
match macos_bpf::MacosBpfTun::new(interface_name, src_addr, dst_addr) {
|
match macos_bpf::MacosBpfTun::new(interface_name, src_addr, dst_addr) {
|
||||||
Ok(tun) => Arc::new(tun),
|
Ok(tun) => Ok(Arc::new(tun)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
?e,
|
?e,
|
||||||
interface_name,
|
interface_name,
|
||||||
"MacosBpfTun init failed, falling back to PnetTun"
|
"MacosBpfTun init failed, falling back to PnetTun"
|
||||||
);
|
);
|
||||||
Arc::new(pnet::PnetTun::new(
|
Ok(Arc::new(pnet::PnetTun::new(
|
||||||
interface_name,
|
interface_name,
|
||||||
pnet::create_packet_filter(src_addr, dst_addr),
|
pnet::create_packet_filter(src_addr, dst_addr),
|
||||||
))
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,19 +56,19 @@ cfg_if::cfg_if! {
|
|||||||
_interface_name: &str,
|
_interface_name: &str,
|
||||||
_src_addr: Option<SocketAddr>,
|
_src_addr: Option<SocketAddr>,
|
||||||
local_addr: SocketAddr,
|
local_addr: SocketAddr,
|
||||||
) -> Arc<dyn super::stack::Tun> {
|
) -> io::Result<Arc<dyn super::stack::Tun>> {
|
||||||
match windivert::WinDivertTun::new(local_addr) {
|
match windivert::WinDivertTun::new(local_addr) {
|
||||||
Ok(tun) => Arc::new(tun),
|
Ok(tun) => Ok(Arc::new(tun)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
?e,
|
?e,
|
||||||
?local_addr,
|
?local_addr,
|
||||||
"WinDivertTun init failed, falling back to PnetTun"
|
"WinDivertTun init failed, falling back to PnetTun"
|
||||||
);
|
);
|
||||||
Arc::new(pnet::PnetTun::new(
|
Ok(Arc::new(pnet::PnetTun::new(
|
||||||
local_addr.to_string().as_str(),
|
local_addr.to_string().as_str(),
|
||||||
pnet::create_packet_filter(None, local_addr),
|
pnet::create_packet_filter(None, local_addr),
|
||||||
))
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,11 @@ cfg_if::cfg_if! {
|
|||||||
interface_name: &str,
|
interface_name: &str,
|
||||||
src_addr: Option<SocketAddr>,
|
src_addr: Option<SocketAddr>,
|
||||||
dst_addr: SocketAddr,
|
dst_addr: SocketAddr,
|
||||||
) -> Arc<dyn super::stack::Tun> {
|
) -> io::Result<Arc<dyn super::stack::Tun>> {
|
||||||
Arc::new(pnet::PnetTun::new(
|
Ok(Arc::new(pnet::PnetTun::new(
|
||||||
interface_name,
|
interface_name,
|
||||||
pnet::create_packet_filter(src_addr, dst_addr),
|
pnet::create_packet_filter(src_addr, dst_addr),
|
||||||
))
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
io,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
@@ -145,14 +146,11 @@ struct InterfaceWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InterfaceWorker {
|
impl InterfaceWorker {
|
||||||
fn new(interface: NetworkInterface) -> Arc<Self> {
|
fn new(interface: NetworkInterface) -> io::Result<Arc<Self>> {
|
||||||
let (tx, mut rx) = match datalink::channel(&interface, Default::default()) {
|
let (tx, mut rx) = match datalink::channel(&interface, Default::default()) {
|
||||||
Ok(pnet::datalink::Channel::Ethernet(tx, rx)) => (tx, rx),
|
Ok(pnet::datalink::Channel::Ethernet(tx, rx)) => (tx, rx),
|
||||||
Ok(_) => panic!("Unhandled channel type"),
|
Ok(_) => return Err(io::Error::other("Unhandled channel type")),
|
||||||
Err(e) => panic!(
|
Err(e) => return Err(io::Error::other(e)),
|
||||||
"An error occurred when creating the datalink channel: {}",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let subscribers = Arc::new(DashMap::<u32, Subscriber>::new());
|
let subscribers = Arc::new(DashMap::<u32, Subscriber>::new());
|
||||||
@@ -187,10 +185,10 @@ impl InterfaceWorker {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
tx: Mutex::new(tx),
|
tx: Mutex::new(tx),
|
||||||
subscribers,
|
subscribers,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe(&self, filter: PacketFilter, sender: tokio::sync::mpsc::Sender<Vec<u8>>) -> u32 {
|
fn subscribe(&self, filter: PacketFilter, sender: tokio::sync::mpsc::Sender<Vec<u8>>) -> u32 {
|
||||||
@@ -207,13 +205,13 @@ impl InterfaceWorker {
|
|||||||
|
|
||||||
static INTERFACE_MANAGERS: Lazy<DashMap<String, Weak<InterfaceWorker>>> = Lazy::new(DashMap::new);
|
static INTERFACE_MANAGERS: Lazy<DashMap<String, Weak<InterfaceWorker>>> = Lazy::new(DashMap::new);
|
||||||
|
|
||||||
fn get_or_create_worker(interface_name: &str) -> Arc<InterfaceWorker> {
|
fn get_or_create_worker(interface_name: &str) -> io::Result<Arc<InterfaceWorker>> {
|
||||||
// Check if we have an active worker
|
// Check if we have an active worker
|
||||||
if let Some(worker) = INTERFACE_MANAGERS
|
if let Some(worker) = INTERFACE_MANAGERS
|
||||||
.get(interface_name)
|
.get(interface_name)
|
||||||
.and_then(|w| w.upgrade())
|
.and_then(|w| w.upgrade())
|
||||||
{
|
{
|
||||||
return worker;
|
return Ok(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to create new worker.
|
// Need to create new worker.
|
||||||
@@ -229,9 +227,9 @@ fn get_or_create_worker(interface_name: &str) -> Arc<InterfaceWorker> {
|
|||||||
.find(|iface| iface.name == interface_name)
|
.find(|iface| iface.name == interface_name)
|
||||||
.expect("Network interface not found");
|
.expect("Network interface not found");
|
||||||
|
|
||||||
let worker = InterfaceWorker::new(interface);
|
let worker = InterfaceWorker::new(interface)?;
|
||||||
INTERFACE_MANAGERS.insert(interface_name.to_string(), Arc::downgrade(&worker));
|
INTERFACE_MANAGERS.insert(interface_name.to_string(), Arc::downgrade(&worker));
|
||||||
worker
|
Ok(worker)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PnetTun {
|
pub struct PnetTun {
|
||||||
@@ -241,17 +239,17 @@ pub struct PnetTun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PnetTun {
|
impl PnetTun {
|
||||||
pub fn new(interface_name: &str, filter: PacketFilter) -> Self {
|
pub fn new(interface_name: &str, filter: PacketFilter) -> io::Result<Self> {
|
||||||
tracing::debug!(interface_name, "Creating new PnetTun");
|
tracing::debug!(interface_name, "Creating new PnetTun");
|
||||||
let worker = get_or_create_worker(interface_name);
|
let worker = get_or_create_worker(interface_name)?;
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(1024);
|
let (tx, rx) = tokio::sync::mpsc::channel(1024);
|
||||||
let id = worker.subscribe(filter, tx);
|
let id = worker.subscribe(filter, tx);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
worker,
|
worker,
|
||||||
subscription_id: id,
|
subscription_id: id,
|
||||||
recv_queue: Mutex::new(rx),
|
recv_queue: Mutex::new(rx),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+83
-190
@@ -127,8 +127,8 @@ importers:
|
|||||||
specifier: ^5.4.8
|
specifier: ^5.4.8
|
||||||
version: 5.4.19(@types/node@22.18.1)
|
version: 5.4.19(@types/node@22.18.1)
|
||||||
vite-plugin-vue-devtools:
|
vite-plugin-vue-devtools:
|
||||||
specifier: ^7.4.6
|
specifier: ^8.0.5
|
||||||
version: 7.7.7(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))
|
version: 8.0.5(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))
|
||||||
vite-plugin-vue-layouts:
|
vite-plugin-vue-layouts:
|
||||||
specifier: ^0.11.0
|
specifier: ^0.11.0
|
||||||
version: 0.11.0(vite@5.4.19(@types/node@22.18.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
|
version: 0.11.0(vite@5.4.19(@types/node@22.18.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
|
||||||
@@ -949,8 +949,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==}
|
resolution: {integrity: sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/shared@11.1.12':
|
'@intlify/shared@11.2.7':
|
||||||
resolution: {integrity: sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A==}
|
resolution: {integrity: sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/shared@12.0.0-alpha.3':
|
'@intlify/shared@12.0.0-alpha.3':
|
||||||
@@ -1089,25 +1089,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-mMB1AvqzTH25rbUo1eRfvFzNqBopX6aRlDmO1fIVVzIWi6YJNKckxbkGaatez4hH/n86IR6aEdZFM3qBUjn3Tg==}
|
resolution: {integrity: sha512-mMB1AvqzTH25rbUo1eRfvFzNqBopX6aRlDmO1fIVVzIWi6YJNKckxbkGaatez4hH/n86IR6aEdZFM3qBUjn3Tg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-arm64-musl@4.2.0':
|
'@oxc-resolver/binding-linux-arm64-musl@4.2.0':
|
||||||
resolution: {integrity: sha512-9oPBU8Yb35z15/14LzALn/8rRwwrtfe19l25N1MRZVSONGiOwfzWNqDNjWiDdyW+EUt/hlylmFOItZmreL6iIw==}
|
resolution: {integrity: sha512-9oPBU8Yb35z15/14LzALn/8rRwwrtfe19l25N1MRZVSONGiOwfzWNqDNjWiDdyW+EUt/hlylmFOItZmreL6iIw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-x64-gnu@4.2.0':
|
'@oxc-resolver/binding-linux-x64-gnu@4.2.0':
|
||||||
resolution: {integrity: sha512-8wU4fwHb0b45i0qMBJ24UYBEtaLyvYWUOqVVCn0SpQZ1mhWWC8dvD6+zIVAKRVex/cKdgzi3imXoKGIDqVEu9w==}
|
resolution: {integrity: sha512-8wU4fwHb0b45i0qMBJ24UYBEtaLyvYWUOqVVCn0SpQZ1mhWWC8dvD6+zIVAKRVex/cKdgzi3imXoKGIDqVEu9w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-x64-musl@4.2.0':
|
'@oxc-resolver/binding-linux-x64-musl@4.2.0':
|
||||||
resolution: {integrity: sha512-5CS2wlGxzESPJCj4NlNGr73QCku75VpGtkwNp8qJF4hLELKAzkoqIB0eBbcvNPg8m2rB7YeXb1u+puGUKXDhNQ==}
|
resolution: {integrity: sha512-5CS2wlGxzESPJCj4NlNGr73QCku75VpGtkwNp8qJF4hLELKAzkoqIB0eBbcvNPg8m2rB7YeXb1u+puGUKXDhNQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@oxc-resolver/binding-wasm32-wasi@4.2.0':
|
'@oxc-resolver/binding-wasm32-wasi@4.2.0':
|
||||||
resolution: {integrity: sha512-VOLpvmVAQZjvj/7Et/gYzW6yBqL9VKjLWOGaFiQ7cvTpY9R9d/1mrNKEuP3beDHF2si2fM5f2pl9bL+N4tvwiA==}
|
resolution: {integrity: sha512-VOLpvmVAQZjvj/7Et/gYzW6yBqL9VKjLWOGaFiQ7cvTpY9R9d/1mrNKEuP3beDHF2si2fM5f2pl9bL+N4tvwiA==}
|
||||||
@@ -1239,67 +1235,56 @@ packages:
|
|||||||
resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==}
|
resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.50.1':
|
'@rollup/rollup-linux-arm-musleabihf@4.50.1':
|
||||||
resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==}
|
resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.50.1':
|
'@rollup/rollup-linux-arm64-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==}
|
resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.50.1':
|
'@rollup/rollup-linux-arm64-musl@4.50.1':
|
||||||
resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==}
|
resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loongarch64-gnu@4.50.1':
|
'@rollup/rollup-linux-loongarch64-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==}
|
resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.50.1':
|
'@rollup/rollup-linux-ppc64-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==}
|
resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.50.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==}
|
resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.50.1':
|
'@rollup/rollup-linux-riscv64-musl@4.50.1':
|
||||||
resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==}
|
resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.50.1':
|
'@rollup/rollup-linux-s390x-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==}
|
resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.50.1':
|
'@rollup/rollup-linux-x64-gnu@4.50.1':
|
||||||
resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==}
|
resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.50.1':
|
'@rollup/rollup-linux-x64-musl@4.50.1':
|
||||||
resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==}
|
resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-openharmony-arm64@4.50.1':
|
'@rollup/rollup-openharmony-arm64@4.50.1':
|
||||||
resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==}
|
resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==}
|
||||||
@@ -1343,13 +1328,6 @@ packages:
|
|||||||
'@rushstack/ts-command-line@5.0.2':
|
'@rushstack/ts-command-line@5.0.2':
|
||||||
resolution: {integrity: sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==}
|
resolution: {integrity: sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==}
|
||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1':
|
|
||||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
|
||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0':
|
|
||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@2.13.0':
|
'@stylistic/eslint-plugin@2.13.0':
|
||||||
resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==}
|
resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -1386,35 +1364,30 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@2.7.1':
|
'@tauri-apps/cli-linux-arm64-musl@2.7.1':
|
||||||
resolution: {integrity: sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA==}
|
resolution: {integrity: sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-riscv64-gnu@2.7.1':
|
'@tauri-apps/cli-linux-riscv64-gnu@2.7.1':
|
||||||
resolution: {integrity: sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg==}
|
resolution: {integrity: sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@2.7.1':
|
'@tauri-apps/cli-linux-x64-gnu@2.7.1':
|
||||||
resolution: {integrity: sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA==}
|
resolution: {integrity: sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@2.7.1':
|
'@tauri-apps/cli-linux-x64-musl@2.7.1':
|
||||||
resolution: {integrity: sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA==}
|
resolution: {integrity: sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@2.7.1':
|
'@tauri-apps/cli-win32-arm64-msvc@2.7.1':
|
||||||
resolution: {integrity: sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA==}
|
resolution: {integrity: sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA==}
|
||||||
@@ -1600,49 +1573,41 @@ packages:
|
|||||||
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
||||||
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
||||||
@@ -1933,16 +1898,16 @@ packages:
|
|||||||
'@vue/devtools-api@6.6.4':
|
'@vue/devtools-api@6.6.4':
|
||||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.7':
|
'@vue/devtools-core@8.0.5':
|
||||||
resolution: {integrity: sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==}
|
resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
|
|
||||||
'@vue/devtools-kit@7.7.7':
|
'@vue/devtools-kit@8.0.5':
|
||||||
resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
|
resolution: {integrity: sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==}
|
||||||
|
|
||||||
'@vue/devtools-shared@7.7.7':
|
'@vue/devtools-shared@8.0.5':
|
||||||
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
|
resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==}
|
||||||
|
|
||||||
'@vue/language-core@2.1.10':
|
'@vue/language-core@2.1.10':
|
||||||
resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==}
|
resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==}
|
||||||
@@ -2062,6 +2027,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ansis@4.2.0:
|
||||||
|
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
any-promise@1.3.0:
|
any-promise@1.3.0:
|
||||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||||
|
|
||||||
@@ -2113,6 +2082,9 @@ packages:
|
|||||||
birpc@2.5.0:
|
birpc@2.5.0:
|
||||||
resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
|
resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
|
||||||
|
|
||||||
|
birpc@2.9.0:
|
||||||
|
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||||
|
|
||||||
boolbase@1.0.0:
|
boolbase@1.0.0:
|
||||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||||
|
|
||||||
@@ -2331,8 +2303,8 @@ packages:
|
|||||||
error-ex@1.3.2:
|
error-ex@1.3.2:
|
||||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||||
|
|
||||||
error-stack-parser-es@0.1.5:
|
error-stack-parser-es@1.0.5:
|
||||||
resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==}
|
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
|
||||||
|
|
||||||
es-define-property@1.0.1:
|
es-define-property@1.0.1:
|
||||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
@@ -2606,10 +2578,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
|
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
|
||||||
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
|
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
|
||||||
|
|
||||||
execa@9.6.0:
|
|
||||||
resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==}
|
|
||||||
engines: {node: ^18.19.0 || >=20.5.0}
|
|
||||||
|
|
||||||
exsolve@1.0.7:
|
exsolve@1.0.7:
|
||||||
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
|
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
|
||||||
|
|
||||||
@@ -2639,10 +2607,6 @@ packages:
|
|||||||
fault@2.0.1:
|
fault@2.0.1:
|
||||||
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||||
|
|
||||||
figures@6.1.0:
|
|
||||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
file-entry-cache@8.0.0:
|
file-entry-cache@8.0.0:
|
||||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@@ -2731,10 +2695,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
get-stream@9.0.1:
|
|
||||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
get-tsconfig@4.10.1:
|
get-tsconfig@4.10.1:
|
||||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
||||||
|
|
||||||
@@ -2812,10 +2772,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
|
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
|
||||||
engines: {node: '>=14.18.0'}
|
engines: {node: '>=14.18.0'}
|
||||||
|
|
||||||
human-signals@8.0.1:
|
|
||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
|
||||||
engines: {node: '>=18.18.0'}
|
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -2895,22 +2851,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
is-plain-obj@4.1.0:
|
|
||||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
is-stream@3.0.0:
|
is-stream@3.0.0:
|
||||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
is-stream@4.0.1:
|
|
||||||
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
is-unicode-supported@2.1.0:
|
|
||||||
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
is-what@4.1.16:
|
is-what@4.1.16:
|
||||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||||
engines: {node: '>=12.13'}
|
engines: {node: '>=12.13'}
|
||||||
@@ -3309,10 +3253,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
npm-run-path@6.0.0:
|
|
||||||
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
nth-check@2.1.1:
|
nth-check@2.1.1:
|
||||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||||
|
|
||||||
@@ -3324,6 +3264,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
ohash@2.0.11:
|
||||||
|
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||||
|
|
||||||
onetime@6.0.0:
|
onetime@6.0.0:
|
||||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -3380,10 +3323,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
parse-ms@4.0.0:
|
|
||||||
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
parse-statements@1.0.11:
|
parse-statements@1.0.11:
|
||||||
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
|
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
|
||||||
|
|
||||||
@@ -3415,8 +3354,8 @@ packages:
|
|||||||
pathe@2.0.3:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
perfect-debounce@1.0.0:
|
perfect-debounce@2.0.0:
|
||||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
@@ -3526,10 +3465,6 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
pretty-ms@9.2.0:
|
|
||||||
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
primeicons@7.0.0:
|
primeicons@7.0.0:
|
||||||
resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==}
|
resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==}
|
||||||
|
|
||||||
@@ -3737,10 +3672,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
strip-final-newline@4.0.0:
|
|
||||||
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
strip-indent@3.0.0:
|
strip-indent@3.0.0:
|
||||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3891,10 +3822,6 @@ packages:
|
|||||||
undici-types@6.21.0:
|
undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
unicorn-magic@0.3.0:
|
|
||||||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
unimport@3.14.6:
|
unimport@3.14.6:
|
||||||
resolution: {integrity: sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==}
|
resolution: {integrity: sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==}
|
||||||
|
|
||||||
@@ -3953,6 +3880,10 @@ packages:
|
|||||||
webpack:
|
webpack:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
unplugin-utils@0.3.1:
|
||||||
|
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
unplugin-vue-components@0.27.5:
|
unplugin-vue-components@0.27.5:
|
||||||
resolution: {integrity: sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==}
|
resolution: {integrity: sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -4023,6 +3954,11 @@ packages:
|
|||||||
validate-npm-package-license@3.0.4:
|
validate-npm-package-license@3.0.4:
|
||||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||||
|
|
||||||
|
vite-dev-rpc@1.1.0:
|
||||||
|
resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==}
|
||||||
|
peerDependencies:
|
||||||
|
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0
|
||||||
|
|
||||||
vite-hot-client@2.1.0:
|
vite-hot-client@2.1.0:
|
||||||
resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==}
|
resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4037,12 +3973,12 @@ packages:
|
|||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vite-plugin-inspect@0.8.9:
|
vite-plugin-inspect@11.3.3:
|
||||||
resolution: {integrity: sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==}
|
resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@nuxt/kit': '*'
|
'@nuxt/kit': '*'
|
||||||
vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1
|
vite: ^6.0.0 || ^7.0.0-0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@nuxt/kit':
|
'@nuxt/kit':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -4054,11 +3990,11 @@ packages:
|
|||||||
rollup: ^4.44.1
|
rollup: ^4.44.1
|
||||||
vite: ^5.4.11 || ^6.0.0 || ^7.0.0
|
vite: ^5.4.11 || ^6.0.0 || ^7.0.0
|
||||||
|
|
||||||
vite-plugin-vue-devtools@7.7.7:
|
vite-plugin-vue-devtools@8.0.5:
|
||||||
resolution: {integrity: sha512-d0fIh3wRcgSlr4Vz7bAk4va1MkdqhQgj9ANE/rBhsAjOnRfTLs2ocjFMvSUOsv6SRRXU9G+VM7yMgqDb6yI4iQ==}
|
resolution: {integrity: sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==}
|
||||||
engines: {node: '>=v14.21.3'}
|
engines: {node: '>=v14.21.3'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
|
vite: ^6.0.0 || ^7.0.0-0
|
||||||
|
|
||||||
vite-plugin-vue-inspector@5.3.2:
|
vite-plugin-vue-inspector@5.3.2:
|
||||||
resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==}
|
resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==}
|
||||||
@@ -4224,10 +4160,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
yoctocolors@2.1.2:
|
|
||||||
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
zwitch@2.0.4:
|
zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|
||||||
@@ -4805,7 +4737,7 @@ snapshots:
|
|||||||
|
|
||||||
'@intlify/shared@10.0.8': {}
|
'@intlify/shared@10.0.8': {}
|
||||||
|
|
||||||
'@intlify/shared@11.1.12': {}
|
'@intlify/shared@11.2.7': {}
|
||||||
|
|
||||||
'@intlify/shared@12.0.0-alpha.3': {}
|
'@intlify/shared@12.0.0-alpha.3': {}
|
||||||
|
|
||||||
@@ -4815,8 +4747,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.5.1))
|
'@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/bundle-utils': 9.0.0(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))
|
||||||
'@intlify/shared': 11.1.12
|
'@intlify/shared': 11.2.7
|
||||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.1.12)(@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.2.7)(@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)
|
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
|
||||||
'@typescript-eslint/scope-manager': 8.42.0
|
'@typescript-eslint/scope-manager': 8.42.0
|
||||||
'@typescript-eslint/typescript-estree': 8.42.0(typescript@5.6.3)
|
'@typescript-eslint/typescript-estree': 8.42.0(typescript@5.6.3)
|
||||||
@@ -4838,11 +4770,11 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.1.12)(@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.2.7)(@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:
|
dependencies:
|
||||||
'@babel/parser': 7.28.4
|
'@babel/parser': 7.28.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@intlify/shared': 11.1.12
|
'@intlify/shared': 11.2.7
|
||||||
'@vue/compiler-dom': 3.5.21
|
'@vue/compiler-dom': 3.5.21
|
||||||
vue: 3.5.21(typescript@5.6.3)
|
vue: 3.5.21(typescript@5.6.3)
|
||||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.6.3))
|
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.6.3))
|
||||||
@@ -5163,10 +5095,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
|
|
||||||
'@sec-ant/readable-stream@0.4.1': {}
|
|
||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)':
|
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||||
@@ -5846,10 +5774,10 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/devtools-api@6.6.4': {}
|
'@vue/devtools-api@6.6.4': {}
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.7(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))':
|
'@vue/devtools-core@8.0.5(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-kit': 7.7.7
|
'@vue/devtools-kit': 8.0.5
|
||||||
'@vue/devtools-shared': 7.7.7
|
'@vue/devtools-shared': 8.0.5
|
||||||
mitt: 3.0.1
|
mitt: 3.0.1
|
||||||
nanoid: 5.1.5
|
nanoid: 5.1.5
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
@@ -5858,17 +5786,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- vite
|
- vite
|
||||||
|
|
||||||
'@vue/devtools-kit@7.7.7':
|
'@vue/devtools-kit@8.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-shared': 7.7.7
|
'@vue/devtools-shared': 8.0.5
|
||||||
birpc: 2.5.0
|
birpc: 2.9.0
|
||||||
hookable: 5.5.3
|
hookable: 5.5.3
|
||||||
mitt: 3.0.1
|
mitt: 3.0.1
|
||||||
perfect-debounce: 1.0.0
|
perfect-debounce: 2.0.0
|
||||||
speakingurl: 14.0.1
|
speakingurl: 14.0.1
|
||||||
superjson: 2.2.2
|
superjson: 2.2.2
|
||||||
|
|
||||||
'@vue/devtools-shared@7.7.7':
|
'@vue/devtools-shared@8.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
rfdc: 1.4.1
|
rfdc: 1.4.1
|
||||||
|
|
||||||
@@ -6018,6 +5946,8 @@ snapshots:
|
|||||||
|
|
||||||
ansi-styles@6.2.1: {}
|
ansi-styles@6.2.1: {}
|
||||||
|
|
||||||
|
ansis@4.2.0: {}
|
||||||
|
|
||||||
any-promise@1.3.0: {}
|
any-promise@1.3.0: {}
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
@@ -6071,6 +6001,8 @@ snapshots:
|
|||||||
|
|
||||||
birpc@2.5.0: {}
|
birpc@2.5.0: {}
|
||||||
|
|
||||||
|
birpc@2.9.0: {}
|
||||||
|
|
||||||
boolbase@1.0.0: {}
|
boolbase@1.0.0: {}
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
@@ -6264,7 +6196,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.2.1
|
is-arrayish: 0.2.1
|
||||||
|
|
||||||
error-stack-parser-es@0.1.5: {}
|
error-stack-parser-es@1.0.5: {}
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
@@ -6674,21 +6606,6 @@ snapshots:
|
|||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
strip-final-newline: 3.0.0
|
strip-final-newline: 3.0.0
|
||||||
|
|
||||||
execa@9.6.0:
|
|
||||||
dependencies:
|
|
||||||
'@sindresorhus/merge-streams': 4.0.0
|
|
||||||
cross-spawn: 7.0.6
|
|
||||||
figures: 6.1.0
|
|
||||||
get-stream: 9.0.1
|
|
||||||
human-signals: 8.0.1
|
|
||||||
is-plain-obj: 4.1.0
|
|
||||||
is-stream: 4.0.1
|
|
||||||
npm-run-path: 6.0.0
|
|
||||||
pretty-ms: 9.2.0
|
|
||||||
signal-exit: 4.1.0
|
|
||||||
strip-final-newline: 4.0.0
|
|
||||||
yoctocolors: 2.1.2
|
|
||||||
|
|
||||||
exsolve@1.0.7: {}
|
exsolve@1.0.7: {}
|
||||||
|
|
||||||
extend-shallow@2.0.1:
|
extend-shallow@2.0.1:
|
||||||
@@ -6719,10 +6636,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
format: 0.2.2
|
format: 0.2.2
|
||||||
|
|
||||||
figures@6.1.0:
|
|
||||||
dependencies:
|
|
||||||
is-unicode-supported: 2.1.0
|
|
||||||
|
|
||||||
file-entry-cache@8.0.0:
|
file-entry-cache@8.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 4.0.1
|
flat-cache: 4.0.1
|
||||||
@@ -6808,11 +6721,6 @@ snapshots:
|
|||||||
|
|
||||||
get-stream@6.0.1: {}
|
get-stream@6.0.1: {}
|
||||||
|
|
||||||
get-stream@9.0.1:
|
|
||||||
dependencies:
|
|
||||||
'@sec-ant/readable-stream': 0.4.1
|
|
||||||
is-stream: 4.0.1
|
|
||||||
|
|
||||||
get-tsconfig@4.10.1:
|
get-tsconfig@4.10.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
@@ -6879,8 +6787,6 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@4.3.1: {}
|
human-signals@4.3.1: {}
|
||||||
|
|
||||||
human-signals@8.0.1: {}
|
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
ignore@7.0.5: {}
|
ignore@7.0.5: {}
|
||||||
@@ -6943,14 +6849,8 @@ snapshots:
|
|||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
is-plain-obj@4.1.0: {}
|
|
||||||
|
|
||||||
is-stream@3.0.0: {}
|
is-stream@3.0.0: {}
|
||||||
|
|
||||||
is-stream@4.0.1: {}
|
|
||||||
|
|
||||||
is-unicode-supported@2.1.0: {}
|
|
||||||
|
|
||||||
is-what@4.1.16: {}
|
is-what@4.1.16: {}
|
||||||
|
|
||||||
is-wsl@3.1.0:
|
is-wsl@3.1.0:
|
||||||
@@ -7496,11 +7396,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-key: 4.0.0
|
path-key: 4.0.0
|
||||||
|
|
||||||
npm-run-path@6.0.0:
|
|
||||||
dependencies:
|
|
||||||
path-key: 4.0.0
|
|
||||||
unicorn-magic: 0.3.0
|
|
||||||
|
|
||||||
nth-check@2.1.1:
|
nth-check@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
@@ -7509,6 +7404,8 @@ snapshots:
|
|||||||
|
|
||||||
object-hash@3.0.0: {}
|
object-hash@3.0.0: {}
|
||||||
|
|
||||||
|
ohash@2.0.11: {}
|
||||||
|
|
||||||
onetime@6.0.0:
|
onetime@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn: 4.0.0
|
mimic-fn: 4.0.0
|
||||||
@@ -7582,8 +7479,6 @@ snapshots:
|
|||||||
json-parse-even-better-errors: 2.3.1
|
json-parse-even-better-errors: 2.3.1
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
parse-ms@4.0.0: {}
|
|
||||||
|
|
||||||
parse-statements@1.0.11: {}
|
parse-statements@1.0.11: {}
|
||||||
|
|
||||||
path-browserify@1.0.1: {}
|
path-browserify@1.0.1: {}
|
||||||
@@ -7605,7 +7500,7 @@ snapshots:
|
|||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
perfect-debounce@1.0.0: {}
|
perfect-debounce@2.0.0: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
@@ -7703,10 +7598,6 @@ snapshots:
|
|||||||
|
|
||||||
prettier@3.6.2: {}
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
pretty-ms@9.2.0:
|
|
||||||
dependencies:
|
|
||||||
parse-ms: 4.0.0
|
|
||||||
|
|
||||||
primeicons@7.0.0: {}
|
primeicons@7.0.0: {}
|
||||||
|
|
||||||
primevue@4.3.9(vue@3.5.21(typescript@5.6.3)):
|
primevue@4.3.9(vue@3.5.21(typescript@5.6.3)):
|
||||||
@@ -7912,8 +7803,6 @@ snapshots:
|
|||||||
|
|
||||||
strip-final-newline@3.0.0: {}
|
strip-final-newline@3.0.0: {}
|
||||||
|
|
||||||
strip-final-newline@4.0.0: {}
|
|
||||||
|
|
||||||
strip-indent@3.0.0:
|
strip-indent@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
min-indent: 1.0.1
|
min-indent: 1.0.1
|
||||||
@@ -8073,8 +7962,6 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
unicorn-magic@0.3.0: {}
|
|
||||||
|
|
||||||
unimport@3.14.6(rollup@4.50.1):
|
unimport@3.14.6(rollup@4.50.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
|
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
|
||||||
@@ -8137,6 +8024,11 @@ snapshots:
|
|||||||
unplugin: 1.16.1
|
unplugin: 1.16.1
|
||||||
vite: 5.4.19(@types/node@22.18.1)
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
|
|
||||||
|
unplugin-utils@0.3.1:
|
||||||
|
dependencies:
|
||||||
|
pathe: 2.0.3
|
||||||
|
picomatch: 4.0.3
|
||||||
|
|
||||||
unplugin-vue-components@0.27.5(@babel/parser@7.28.4)(rollup@4.50.1)(vue@3.5.21(typescript@5.6.3)):
|
unplugin-vue-components@0.27.5(@babel/parser@7.28.4)(rollup@4.50.1)(vue@3.5.21(typescript@5.6.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/utils': 0.7.10
|
'@antfu/utils': 0.7.10
|
||||||
@@ -8300,6 +8192,12 @@ snapshots:
|
|||||||
spdx-correct: 3.2.0
|
spdx-correct: 3.2.0
|
||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
|
|
||||||
|
vite-dev-rpc@1.1.0(vite@5.4.19(@types/node@22.18.1)):
|
||||||
|
dependencies:
|
||||||
|
birpc: 2.5.0
|
||||||
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
|
vite-hot-client: 2.1.0(vite@5.4.19(@types/node@22.18.1))
|
||||||
|
|
||||||
vite-hot-client@2.1.0(vite@5.4.19(@types/node@22.18.1)):
|
vite-hot-client@2.1.0(vite@5.4.19(@types/node@22.18.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 5.4.19(@types/node@22.18.1)
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
@@ -8323,20 +8221,19 @@ snapshots:
|
|||||||
- rollup
|
- rollup
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite-plugin-inspect@0.8.9(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1)):
|
vite-plugin-inspect@11.3.3(vite@5.4.19(@types/node@22.18.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/utils': 0.7.10
|
ansis: 4.2.0
|
||||||
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
|
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
error-stack-parser-es: 0.1.5
|
error-stack-parser-es: 1.0.5
|
||||||
fs-extra: 11.3.1
|
ohash: 2.0.11
|
||||||
open: 10.2.0
|
open: 10.2.0
|
||||||
perfect-debounce: 1.0.0
|
perfect-debounce: 2.0.0
|
||||||
picocolors: 1.1.1
|
|
||||||
sirv: 3.0.2
|
sirv: 3.0.2
|
||||||
|
unplugin-utils: 0.3.1
|
||||||
vite: 5.4.19(@types/node@22.18.1)
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
|
vite-dev-rpc: 1.1.0(vite@5.4.19(@types/node@22.18.1))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- rollup
|
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite-plugin-singlefile@2.3.0(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1)):
|
vite-plugin-singlefile@2.3.0(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1)):
|
||||||
@@ -8345,19 +8242,17 @@ snapshots:
|
|||||||
rollup: 4.50.1
|
rollup: 4.50.1
|
||||||
vite: 5.4.19(@types/node@22.18.1)
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
|
|
||||||
vite-plugin-vue-devtools@7.7.7(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3)):
|
vite-plugin-vue-devtools@8.0.5(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-core': 7.7.7(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))
|
'@vue/devtools-core': 8.0.5(vite@5.4.19(@types/node@22.18.1))(vue@3.5.21(typescript@5.6.3))
|
||||||
'@vue/devtools-kit': 7.7.7
|
'@vue/devtools-kit': 8.0.5
|
||||||
'@vue/devtools-shared': 7.7.7
|
'@vue/devtools-shared': 8.0.5
|
||||||
execa: 9.6.0
|
|
||||||
sirv: 3.0.2
|
sirv: 3.0.2
|
||||||
vite: 5.4.19(@types/node@22.18.1)
|
vite: 5.4.19(@types/node@22.18.1)
|
||||||
vite-plugin-inspect: 0.8.9(rollup@4.50.1)(vite@5.4.19(@types/node@22.18.1))
|
vite-plugin-inspect: 11.3.3(vite@5.4.19(@types/node@22.18.1))
|
||||||
vite-plugin-vue-inspector: 5.3.2(vite@5.4.19(@types/node@22.18.1))
|
vite-plugin-vue-inspector: 5.3.2(vite@5.4.19(@types/node@22.18.1))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
- rollup
|
|
||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
@@ -8511,6 +8406,4 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
yoctocolors@2.1.2: {}
|
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ foreign_network_whitelist = "*"
|
|||||||
disable_p2p = false
|
disable_p2p = false
|
||||||
p2p_only = false
|
p2p_only = false
|
||||||
relay_all_peer_rpc = false
|
relay_all_peer_rpc = false
|
||||||
|
disable_tcp_hole_punching = false
|
||||||
disable_udp_hole_punching = false
|
disable_udp_hole_punching = false
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
Reference in New Issue
Block a user