fix 202 bugs (#418)

* fix peer rpc stop working because of mpsc tunnel close unexpectedly

* fix gui:

1. allow set network prefix for virtual ipv4
2. fix android crash
3. fix subnet proxy cannot be set on android
This commit is contained in:
Sijie.Sun 2024-10-13 11:59:16 +08:00 committed by GitHub
parent 55efd62798
commit d87a440c04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 181 additions and 35 deletions

View File

@ -41,6 +41,7 @@ struct NetworkConfig {
dhcp: bool, dhcp: bool,
virtual_ipv4: String, virtual_ipv4: String,
network_length: i32,
hostname: Option<String>, hostname: Option<String>,
network_name: String, network_name: String,
network_secret: String, network_secret: String,
@ -83,9 +84,15 @@ impl NetworkConfig {
if !self.dhcp { if !self.dhcp {
if self.virtual_ipv4.len() > 0 { if self.virtual_ipv4.len() > 0 {
cfg.set_ipv4(Some(self.virtual_ipv4.parse().with_context(|| { let ip = format!("{}/{}", self.virtual_ipv4, self.network_length)
format!("failed to parse ipv4 address: {}", self.virtual_ipv4) .parse()
})?)) .with_context(|| {
format!(
"failed to parse ipv4 inet address: {}, {}",
self.virtual_ipv4, self.network_length
)
})?;
cfg.set_ipv4(Some(ip));
} }
} }

View File

@ -85,6 +85,20 @@ function searchPeerSuggestions(e: { query: string }) {
peerSuggestions.value = searchUrlSuggestions(e) peerSuggestions.value = searchUrlSuggestions(e)
} }
const inetSuggestions = ref([''])
function searchInetSuggestions(e: { query: string }) {
if (e.query.search('/') >= 0) {
inetSuggestions.value = [e.query]
} else {
const ret = []
for (let i = 0; i < 32; i++) {
ret.push(`${e.query}/${i}`)
}
inetSuggestions.value = ret
}
}
const listenerSuggestions = ref(['']) const listenerSuggestions = ref([''])
function searchListenerSuggestiong(e: { query: string }) { function searchListenerSuggestiong(e: { query: string }) {
@ -153,8 +167,9 @@ onMounted(async () => {
aria-describedby="virtual_ipv4-help" aria-describedby="virtual_ipv4-help"
/> />
<InputGroupAddon> <InputGroupAddon>
<span>/24</span> <span>/</span>
</InputGroupAddon> </InputGroupAddon>
<InputNumber v-model="curNetwork.network_length" :disabled="curNetwork.dhcp" inputId="horizontal-buttons" showButtons :step="1" mode="decimal" :min="1" :max="32" fluid class="max-w-20"/>
</InputGroup> </InputGroup>
</div> </div>
</div> </div>
@ -221,9 +236,10 @@ onMounted(async () => {
<div class="flex flex-row gap-x-9 flex-wrap w-full"> <div class="flex flex-row gap-x-9 flex-wrap w-full">
<div class="flex flex-column gap-2 grow p-fluid"> <div class="flex flex-column gap-2 grow p-fluid">
<label for="username">{{ t('proxy_cidrs') }}</label> <label for="username">{{ t('proxy_cidrs') }}</label>
<Chips <AutoComplete
id="chips" v-model="curNetwork.proxy_cidrs" id="subnet-proxy"
:placeholder="t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full" v-model="curNetwork.proxy_cidrs" :placeholder="t('chips_placeholder', ['10.0.0.0/24'])"
class="w-full" multiple fluid :suggestions="inetSuggestions" @complete="searchInetSuggestions"
/> />
</div> </div>
</div> </div>

View File

@ -214,6 +214,8 @@ const myNodeInfoChips = computed(() => {
PortRestricted = 5, PortRestricted = 5,
Symmetric = 6, Symmetric = 6,
SymUdpFirewall = 7, SymUdpFirewall = 7,
SymmetricEasyInc = 8,
SymmetricEasyDec = 9,
}; };
const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type
if (udpNatType !== undefined) { if (udpNatType !== undefined) {
@ -226,6 +228,8 @@ const myNodeInfoChips = computed(() => {
[NatType.PortRestricted]: 'Port Restricted', [NatType.PortRestricted]: 'Port Restricted',
[NatType.Symmetric]: 'Symmetric', [NatType.Symmetric]: 'Symmetric',
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall', [NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
[NatType.SymmetricEasyInc]: 'Symmetric Easy Inc',
[NatType.SymmetricEasyDec]: 'Symmetric Easy Dec',
} }
chips.push({ chips.push({

View File

@ -48,7 +48,7 @@ async function doStartVpn(ipv4Addr: string, cidr: number, routes: string[]) {
console.log('start vpn') console.log('start vpn')
const start_ret = await start_vpn({ const start_ret = await start_vpn({
ipv4Addr: `${ipv4Addr}/${cidr}`, ipv4Addr: `${ipv4Addr}`,
routes, routes,
disallowedApplications: ['com.kkrainbow.easytier'], disallowedApplications: ['com.kkrainbow.easytier'],
mtu: 1300, mtu: 1300,

View File

@ -11,6 +11,7 @@ export interface NetworkConfig {
dhcp: boolean dhcp: boolean
virtual_ipv4: string virtual_ipv4: string
network_length: number,
hostname?: string hostname?: string
network_name: string network_name: string
network_secret: string network_secret: string
@ -42,6 +43,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
dhcp: true, dhcp: true,
virtual_ipv4: '', virtual_ipv4: '',
network_length: 24,
network_name: 'easytier', network_name: 'easytier',
network_secret: '', network_secret: '',

View File

@ -93,7 +93,7 @@ impl PeerConn {
let peer_conn_tunnel_filter = StatsRecorderTunnelFilter::new(); let peer_conn_tunnel_filter = StatsRecorderTunnelFilter::new();
let throughput = peer_conn_tunnel_filter.filter_output(); let throughput = peer_conn_tunnel_filter.filter_output();
let peer_conn_tunnel = TunnelWithFilter::new(tunnel, peer_conn_tunnel_filter); let peer_conn_tunnel = TunnelWithFilter::new(tunnel, peer_conn_tunnel_filter);
let mut mpsc_tunnel = MpscTunnel::new(peer_conn_tunnel); let mut mpsc_tunnel = MpscTunnel::new(peer_conn_tunnel, Some(Duration::from_secs(7)));
let (recv, sink) = (mpsc_tunnel.get_stream(), mpsc_tunnel.get_sink()); let (recv, sink) = (mpsc_tunnel.get_stream(), mpsc_tunnel.get_sink());

View File

@ -1387,7 +1387,9 @@ impl PeerRouteServiceImpl {
if resp.error.is_some() { if resp.error.is_some() {
let err = resp.error.unwrap(); let err = resp.error.unwrap();
if err == Error::DuplicatePeerId as i32 { if err == Error::DuplicatePeerId as i32 {
panic!("duplicate peer id"); if !self.global_ctx.get_feature_flags().is_public_server {
panic!("duplicate peer id");
}
} else { } else {
tracing::error!(?ret, ?my_peer_id, ?dst_peer_id, "sync_route_info failed"); tracing::error!(?ret, ?my_peer_id, ?dst_peer_id, "sync_route_info failed");
session session

View File

@ -61,8 +61,8 @@ impl Client {
pub fn new() -> Self { pub fn new() -> Self {
let (ring_a, ring_b) = create_ring_tunnel_pair(); let (ring_a, ring_b) = create_ring_tunnel_pair();
Self { Self {
mpsc: Mutex::new(MpscTunnel::new(ring_a)), mpsc: Mutex::new(MpscTunnel::new(ring_a, None)),
transport: Mutex::new(MpscTunnel::new(ring_b)), transport: Mutex::new(MpscTunnel::new(ring_b, None)),
inflight_requests: Arc::new(DashMap::new()), inflight_requests: Arc::new(DashMap::new()),
tasks: Arc::new(Mutex::new(JoinSet::new())), tasks: Arc::new(Mutex::new(JoinSet::new())),
} }

View File

@ -56,8 +56,8 @@ impl Server {
Self { Self {
registry, registry,
mpsc: Mutex::new(Some(MpscTunnel::new(ring_a))), mpsc: Mutex::new(Some(MpscTunnel::new(ring_a, None))),
transport: Mutex::new(MpscTunnel::new(ring_b)), transport: Mutex::new(MpscTunnel::new(ring_b, None)),
tasks: Arc::new(Mutex::new(JoinSet::new())), tasks: Arc::new(Mutex::new(JoinSet::new())),
packet_mergers: Arc::new(DashMap::new()), packet_mergers: Arc::new(DashMap::new()),
} }

View File

@ -175,6 +175,83 @@ async fn rpc_timeout_test() {
assert_eq!(0, ctx.server.inflight_count()); assert_eq!(0, ctx.server.inflight_count());
} }
#[tokio::test]
async fn rpc_tunnel_stuck_test() {
use crate::proto::rpc_types;
use crate::tunnel::ring::RING_TUNNEL_CAP;
let rpc_server = Server::new();
rpc_server.run();
let server = GreetingServer::new(GreetingService {
delay_ms: 0,
prefix: "Hello".to_string(),
});
rpc_server.registry().register(server, "test");
let client = Client::new();
client.run();
let rpc_tasks = Arc::new(Mutex::new(JoinSet::new()));
let (mut rx, tx) = (
rpc_server.get_transport_stream(),
client.get_transport_sink(),
);
rpc_tasks.lock().unwrap().spawn(async move {
while let Some(Ok(packet)) = rx.next().await {
if let Err(err) = tx.send(packet).await {
println!("{:?}", err);
break;
}
}
});
// mock server is stuck (no task to do forwards)
let mut tasks = JoinSet::new();
for _ in 0..RING_TUNNEL_CAP + 15 {
let out =
client.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "test".to_string());
tasks.spawn(async move {
let mut ctrl = RpcController::default();
ctrl.timeout_ms = 1000;
let input = SayHelloRequest {
name: "world".to_string(),
};
out.say_hello(ctrl, input).await
});
}
while let Some(ret) = tasks.join_next().await {
assert!(matches!(ret, Ok(Err(rpc_types::error::Error::Timeout(_)))));
}
// start server consumer, new requests should be processed
let (mut rx, tx) = (
client.get_transport_stream(),
rpc_server.get_transport_sink(),
);
rpc_tasks.lock().unwrap().spawn(async move {
while let Some(Ok(packet)) = rx.next().await {
if let Err(err) = tx.send(packet).await {
println!("{:?}", err);
break;
}
}
});
let out =
client.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "test".to_string());
let mut ctrl = RpcController::default();
ctrl.timeout_ms = 1000;
let input = SayHelloRequest {
name: "fuck world".to_string(),
};
let ret = out.say_hello(ctrl, input).await.unwrap();
assert_eq!(ret.greeting, "Hello fuck world!");
}
#[tokio::test] #[tokio::test]
async fn standalone_rpc_test() { async fn standalone_rpc_test() {
use crate::proto::rpc_impl::standalone::{StandAloneClient, StandAloneServer}; use crate::proto::rpc_impl::standalone::{StandAloneClient, StandAloneServer};

View File

@ -41,13 +41,13 @@ pub struct MpscTunnel<T> {
} }
impl<T: Tunnel> MpscTunnel<T> { impl<T: Tunnel> MpscTunnel<T> {
pub fn new(tunnel: T) -> Self { pub fn new(tunnel: T, send_timeout: Option<Duration>) -> Self {
let (tx, mut rx) = channel(32); let (tx, mut rx) = channel(32);
let (stream, mut sink) = tunnel.split(); let (stream, mut sink) = tunnel.split();
let task = tokio::spawn(async move { let task = tokio::spawn(async move {
loop { loop {
if let Err(e) = Self::forward_one_round(&mut rx, &mut sink).await { if let Err(e) = Self::forward_one_round(&mut rx, &mut sink, send_timeout).await {
tracing::error!(?e, "forward error"); tracing::error!(?e, "forward error");
break; break;
} }
@ -68,21 +68,44 @@ impl<T: Tunnel> MpscTunnel<T> {
async fn forward_one_round( async fn forward_one_round(
rx: &mut Receiver<ZCPacket>, rx: &mut Receiver<ZCPacket>,
sink: &mut Pin<Box<dyn ZCPacketSink>>, sink: &mut Pin<Box<dyn ZCPacketSink>>,
send_timeout_ms: Option<Duration>,
) -> Result<(), TunnelError> { ) -> Result<(), TunnelError> {
let item = rx.recv().await.with_context(|| "recv error")?; let item = rx.recv().await.with_context(|| "recv error")?;
if let Some(timeout_ms) = send_timeout_ms {
Self::forward_one_round_with_timeout(rx, sink, item, timeout_ms).await
} else {
Self::forward_one_round_no_timeout(rx, sink, item).await
}
}
match timeout(Duration::from_secs(10), async move { async fn forward_one_round_no_timeout(
sink.feed(item).await?; rx: &mut Receiver<ZCPacket>,
while let Ok(item) = rx.try_recv() { sink: &mut Pin<Box<dyn ZCPacketSink>>,
match sink.feed(item).await { initial_item: ZCPacket,
Err(e) => { ) -> Result<(), TunnelError> {
tracing::error!(?e, "feed error"); sink.feed(initial_item).await?;
return Err(e);
} while let Ok(item) = rx.try_recv() {
Ok(_) => {} match sink.feed(item).await {
Err(e) => {
tracing::error!(?e, "feed error");
return Err(e);
} }
Ok(_) => {}
} }
sink.flush().await }
sink.flush().await
}
async fn forward_one_round_with_timeout(
rx: &mut Receiver<ZCPacket>,
sink: &mut Pin<Box<dyn ZCPacketSink>>,
initial_item: ZCPacket,
timeout_ms: Duration,
) -> Result<(), TunnelError> {
match timeout(timeout_ms, async move {
Self::forward_one_round_no_timeout(rx, sink, initial_item).await
}) })
.await .await
{ {
@ -112,17 +135,12 @@ impl<T: Tunnel> MpscTunnel<T> {
} }
} }
impl<T: Tunnel> From<T> for MpscTunnel<T> {
fn from(tunnel: T) -> Self {
Self::new(tunnel)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures::StreamExt; use futures::StreamExt;
use crate::tunnel::{ use crate::tunnel::{
ring::{create_ring_tunnel_pair, RING_TUNNEL_CAP},
tcp::{TcpTunnelConnector, TcpTunnelListener}, tcp::{TcpTunnelConnector, TcpTunnelListener},
TunnelConnector, TunnelListener, TunnelConnector, TunnelListener,
}; };
@ -162,7 +180,7 @@ mod tests {
}); });
let tunnel = connector.connect().await.unwrap(); let tunnel = connector.connect().await.unwrap();
let mpsc_tunnel = MpscTunnel::from(tunnel); let mpsc_tunnel = MpscTunnel::new(tunnel, None);
let sink1 = mpsc_tunnel.get_sink(); let sink1 = mpsc_tunnel.get_sink();
let t2 = tokio::spawn(async move { let t2 = tokio::spawn(async move {
@ -213,4 +231,24 @@ mod tests {
let _ = tokio::join!(t1, t2, t3, t4); let _ = tokio::join!(t1, t2, t3, t4);
} }
#[tokio::test]
async fn mpsc_slow_receiver_with_send_timeout() {
let (a, _b) = create_ring_tunnel_pair();
let mpsc_tunnel = MpscTunnel::new(a, Some(Duration::from_secs(1)));
let s = mpsc_tunnel.get_sink();
for _ in 0..RING_TUNNEL_CAP {
s.send(ZCPacket::new_with_payload(&[0; 1024]))
.await
.unwrap();
}
tokio::time::sleep(Duration::from_millis(1500)).await;
let e = s.send(ZCPacket::new_with_payload(&[0; 1024])).await;
assert!(e.is_ok());
tokio::time::sleep(Duration::from_millis(1500)).await;
let e = s.send(ZCPacket::new_with_payload(&[0; 1024])).await;
assert!(e.is_err());
}
} }

View File

@ -26,7 +26,7 @@ use super::{
StreamItem, Tunnel, TunnelConnector, TunnelError, TunnelInfo, TunnelListener, StreamItem, Tunnel, TunnelConnector, TunnelError, TunnelInfo, TunnelListener,
}; };
static RING_TUNNEL_CAP: usize = 128; pub static RING_TUNNEL_CAP: usize = 128;
static RING_TUNNEL_RESERVERD_CAP: usize = 4; static RING_TUNNEL_RESERVERD_CAP: usize = 4;
type RingLock = parking_lot::Mutex<()>; type RingLock = parking_lot::Mutex<()>;

View File

@ -81,7 +81,7 @@ impl WireGuardImpl {
wg_peer_ip_table: WgPeerIpTable, wg_peer_ip_table: WgPeerIpTable,
) { ) {
let info = t.info().unwrap_or_default(); let info = t.info().unwrap_or_default();
let mut mpsc_tunnel = MpscTunnel::new(t); let mut mpsc_tunnel = MpscTunnel::new(t, None);
let mut stream = mpsc_tunnel.get_stream(); let mut stream = mpsc_tunnel.get_stream();
let mut ip_registered = false; let mut ip_registered = false;