Compare commits
3
Commits
v1.0.0-rc1
...
252f081a61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
252f081a61 | ||
|
|
b1e8326b0a | ||
|
|
3b4931aa8f |
@@ -67,6 +67,7 @@ cleanup() {
|
|||||||
kill "$server_pid" 2>/dev/null || true
|
kill "$server_pid" 2>/dev/null || true
|
||||||
wait "$server_pid" 2>/dev/null || true
|
wait "$server_pid" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
pkill -f "$server_bin hold --runtime-dir $home/sessions/run" 2>/dev/null || true
|
||||||
rm -rf "$home"
|
rm -rf "$home"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
@@ -86,6 +87,7 @@ scrollback = 5000
|
|||||||
auth_ttl_secs = 30
|
auth_ttl_secs = 30
|
||||||
attach_ticket_ttl_secs = 3600
|
attach_ticket_ttl_secs = 3600
|
||||||
allow_attach_tickets = true
|
allow_attach_tickets = true
|
||||||
|
persist_sessions = false
|
||||||
client_timeout_secs = 60
|
client_timeout_secs = 60
|
||||||
retransmit_window = 256
|
retransmit_window = 256
|
||||||
default_input_mode = "read-write"
|
default_input_mode = "read-write"
|
||||||
|
|||||||
+71
-2
@@ -63,6 +63,8 @@ const STREAM_INITIAL_WINDOW: usize = 1024 * 1024;
|
|||||||
const MAX_PENDING_USER_INPUT_BYTES: usize = 1024 * 1024;
|
const MAX_PENDING_USER_INPUT_BYTES: usize = 1024 * 1024;
|
||||||
const STARTUP_INPUT_HOLD: Duration = Duration::from_millis(750);
|
const STARTUP_INPUT_HOLD: Duration = Duration::from_millis(750);
|
||||||
const POST_SUBMIT_ALL_INPUT_HOLD: Duration = Duration::from_millis(120);
|
const POST_SUBMIT_ALL_INPUT_HOLD: Duration = Duration::from_millis(120);
|
||||||
|
const STALE_TERMINAL_INPUT_AFTER: Duration = Duration::from_secs(2);
|
||||||
|
const POST_RECONNECT_STALE_INPUT_GRACE: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
/// Sentinel `target_host` the server uses on a server-initiated `StreamOpen` that
|
/// Sentinel `target_host` the server uses on a server-initiated `StreamOpen` that
|
||||||
/// represents an SSH-agent connection (rather than a TCP target). The client
|
/// represents an SSH-agent connection (rather than a TCP target). The client
|
||||||
@@ -4376,6 +4378,7 @@ async fn run_terminal(
|
|||||||
let mut pending_user_input_bytes = 0usize;
|
let mut pending_user_input_bytes = 0usize;
|
||||||
let mut startup_input_hold_until: Option<Instant> = None;
|
let mut startup_input_hold_until: Option<Instant> = None;
|
||||||
let mut startup_gate_mode = StartupGateMode::HoldControl;
|
let mut startup_gate_mode = StartupGateMode::HoldControl;
|
||||||
|
let mut stale_terminal_input_suppress_until: Option<Instant> = None;
|
||||||
if let Some(frame) = first_frame {
|
if let Some(frame) = first_frame {
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
@@ -4433,10 +4436,19 @@ async fn run_terminal(
|
|||||||
biased;
|
biased;
|
||||||
stdin_msg = stdin_rx.recv() => {
|
stdin_msg = stdin_rx.recv() => {
|
||||||
match stdin_msg {
|
match stdin_msg {
|
||||||
Some(bytes) => {
|
Some(mut bytes) => {
|
||||||
if input_matches_escape(&bytes, escape_key.as_deref()) {
|
if input_matches_escape(&bytes, escape_key.as_deref()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if should_strip_stale_terminal_reports(
|
||||||
|
last_packet_at.elapsed(),
|
||||||
|
stale_terminal_input_suppress_until,
|
||||||
|
) {
|
||||||
|
bytes = strip_stale_mouse_reports(&bytes);
|
||||||
|
if bytes.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if cred.mode != "view-only" && cred.mode != "forward-only" {
|
if cred.mode != "view-only" && cred.mode != "forward-only" {
|
||||||
if let Some(deadline) = startup_input_hold_until {
|
if let Some(deadline) = startup_input_hold_until {
|
||||||
if !predictor.alternate_screen
|
if !predictor.alternate_screen
|
||||||
@@ -4535,6 +4547,9 @@ async fn run_terminal(
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
refresh_live_addr(&mut addr, &cred)?;
|
refresh_live_addr(&mut addr, &cred)?;
|
||||||
|
arm_stale_terminal_input_suppression(
|
||||||
|
&mut stale_terminal_input_suppress_until,
|
||||||
|
);
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
predictor.observe_output(&frame.bytes);
|
predictor.observe_output(&frame.bytes);
|
||||||
@@ -4594,6 +4609,9 @@ async fn run_terminal(
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
refresh_live_addr(&mut addr, &cred)?;
|
refresh_live_addr(&mut addr, &cred)?;
|
||||||
|
arm_stale_terminal_input_suppression(
|
||||||
|
&mut stale_terminal_input_suppress_until,
|
||||||
|
);
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
predictor.observe_output(&frame.bytes);
|
predictor.observe_output(&frame.bytes);
|
||||||
@@ -4644,6 +4662,9 @@ async fn run_terminal(
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
refresh_live_addr(&mut addr, &cred)?;
|
refresh_live_addr(&mut addr, &cred)?;
|
||||||
|
arm_stale_terminal_input_suppression(
|
||||||
|
&mut stale_terminal_input_suppress_until,
|
||||||
|
);
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
predictor.observe_output(&frame.bytes);
|
predictor.observe_output(&frame.bytes);
|
||||||
@@ -4765,6 +4786,9 @@ async fn run_terminal(
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
refresh_live_addr(&mut addr, &cred)?;
|
refresh_live_addr(&mut addr, &cred)?;
|
||||||
|
arm_stale_terminal_input_suppression(
|
||||||
|
&mut stale_terminal_input_suppress_until,
|
||||||
|
);
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
predictor.observe_output(&frame.bytes);
|
predictor.observe_output(&frame.bytes);
|
||||||
@@ -5157,6 +5181,9 @@ async fn run_terminal(
|
|||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
refresh_live_addr(&mut addr, &cred)?;
|
refresh_live_addr(&mut addr, &cred)?;
|
||||||
|
arm_stale_terminal_input_suppression(
|
||||||
|
&mut stale_terminal_input_suppress_until,
|
||||||
|
);
|
||||||
if !forward_only {
|
if !forward_only {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
predictor.observe_output(&frame.bytes);
|
predictor.observe_output(&frame.bytes);
|
||||||
@@ -5562,6 +5589,18 @@ fn should_flush_terminal_input_after_contact(
|
|||||||
!forward_only && !pending_empty && mode != "view-only" && mode != "forward-only"
|
!forward_only && !pending_empty && mode != "view-only" && mode != "forward-only"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_strip_stale_terminal_reports(
|
||||||
|
silent_for: Duration,
|
||||||
|
suppress_until: Option<Instant>,
|
||||||
|
) -> bool {
|
||||||
|
silent_for >= STALE_TERMINAL_INPUT_AFTER
|
||||||
|
|| suppress_until.is_some_and(|deadline| Instant::now() < deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arm_stale_terminal_input_suppression(suppress_until: &mut Option<Instant>) {
|
||||||
|
*suppress_until = Some(Instant::now() + POST_RECONNECT_STALE_INPUT_GRACE);
|
||||||
|
}
|
||||||
|
|
||||||
async fn flush_pending_user_input(
|
async fn flush_pending_user_input(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@@ -5623,6 +5662,7 @@ fn stale_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
|
|||||||
match bytes.get(offset).copied()? {
|
match bytes.get(offset).copied()? {
|
||||||
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
|
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
|
||||||
Some(b'M') if offset + 6 <= bytes.len() => Some(6),
|
Some(b'M') if offset + 6 <= bytes.len() => Some(6),
|
||||||
|
Some(b'I' | b'O') => Some(3),
|
||||||
Some(b'<') => mouse_report_params_len(bytes, offset + 3, 2).map(|len| len + 3),
|
Some(b'<') => mouse_report_params_len(bytes, offset + 3, 2).map(|len| len + 3),
|
||||||
Some(byte) if byte.is_ascii_digit() => {
|
Some(byte) if byte.is_ascii_digit() => {
|
||||||
mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2)
|
mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2)
|
||||||
@@ -5631,13 +5671,15 @@ fn stale_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
|
|||||||
},
|
},
|
||||||
0x9b => match bytes.get(offset + 1).copied() {
|
0x9b => match bytes.get(offset + 1).copied() {
|
||||||
Some(b'M') if offset + 5 <= bytes.len() => Some(5),
|
Some(b'M') if offset + 5 <= bytes.len() => Some(5),
|
||||||
|
Some(b'I' | b'O') => Some(2),
|
||||||
Some(b'<') => mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2),
|
Some(b'<') => mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2),
|
||||||
Some(byte) if byte.is_ascii_digit() => {
|
Some(byte) if byte.is_ascii_digit() => {
|
||||||
mouse_report_params_len(bytes, offset + 1, 2).map(|len| len + 1)
|
mouse_report_params_len(bytes, offset + 1, 2).map(|len| len + 1)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
byte if byte.is_ascii_digit() => mouse_report_params_len(bytes, offset, 1),
|
byte if byte.is_ascii_digit() => mouse_report_params_len(bytes, offset, 1)
|
||||||
|
.or_else(|| orphan_mouse_suffix_len(bytes, offset)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5659,6 +5701,21 @@ fn mouse_report_params_len(bytes: &[u8], offset: usize, min_semicolons: usize) -
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn orphan_mouse_suffix_len(bytes: &[u8], offset: usize) -> Option<usize> {
|
||||||
|
if offset != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut cursor = offset;
|
||||||
|
while let Some(byte) = bytes.get(cursor).copied() {
|
||||||
|
match byte {
|
||||||
|
b'0'..=b'9' => cursor += 1,
|
||||||
|
b'M' | b'm' if cursor > offset => return Some(cursor + 1 - offset),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn stale_mouse_report_maybe_incomplete(bytes: &[u8], offset: usize) -> bool {
|
fn stale_mouse_report_maybe_incomplete(bytes: &[u8], offset: usize) -> bool {
|
||||||
let Some(rest) = bytes.get(offset..) else {
|
let Some(rest) = bytes.get(offset..) else {
|
||||||
return false;
|
return false;
|
||||||
@@ -8495,6 +8552,18 @@ mod tests {
|
|||||||
assert_eq!(strip_stale_mouse_reports(b"152;1Mls\r"), b"ls\r");
|
assert_eq!(strip_stale_mouse_reports(b"152;1Mls\r"), b"ls\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_stale_mouse_suffixes_are_stripped_from_pending_input() {
|
||||||
|
assert_eq!(strip_stale_mouse_reports(b"1M35;149;1Mls\r"), b"ls\r");
|
||||||
|
assert_eq!(strip_stale_mouse_reports(b"10m"), b"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stale_focus_reports_are_stripped_from_pending_input() {
|
||||||
|
assert_eq!(strip_stale_mouse_reports(b"\x1b[Ihello\x1b[O"), b"hello");
|
||||||
|
assert_eq!(strip_stale_mouse_reports(b"\x9bIhello\x9bO"), b"hello");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn non_mouse_escape_input_survives_stale_filter() {
|
fn non_mouse_escape_input_survives_stale_filter() {
|
||||||
assert_eq!(strip_stale_mouse_reports(b"\x1b[A"), b"\x1b[A");
|
assert_eq!(strip_stale_mouse_reports(b"\x1b[A"), b"\x1b[A");
|
||||||
|
|||||||
+91
-16
@@ -980,7 +980,7 @@ async fn handle_native_client_hello(
|
|||||||
};
|
};
|
||||||
let body = protocol::to_body(&NativeServerHelloBody { hello })?;
|
let body = protocol::to_body(&NativeServerHelloBody { hello })?;
|
||||||
let out = protocol::encode_plain(PacketKind::NativeServerHello, pending_id, 1, 0, &body)?;
|
let out = protocol::encode_plain(PacketKind::NativeServerHello, pending_id, 1, 0, &body)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,7 +1058,7 @@ async fn handle_native_user_auth(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
&body,
|
&body,
|
||||||
)?;
|
)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1251,7 +1251,7 @@ async fn handle_native_user_auth(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
&body,
|
&body,
|
||||||
)?;
|
)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1359,7 +1359,7 @@ async fn handle_bootstrap_attach(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
&body,
|
&body,
|
||||||
)?;
|
)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1494,7 +1494,7 @@ async fn handle_ticket_attach(
|
|||||||
};
|
};
|
||||||
let body = protocol::to_body(&envelope)?;
|
let body = protocol::to_body(&envelope)?;
|
||||||
let out = protocol::encode_plain(PacketKind::AttachOk, client_id, 1, 0, &body)?;
|
let out = protocol::encode_plain(PacketKind::AttachOk, client_id, 1, 0, &body)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1512,7 +1512,7 @@ async fn send_reject_to_client(
|
|||||||
reason: reason.to_string(),
|
reason: reason.to_string(),
|
||||||
})?;
|
})?;
|
||||||
let out = protocol::encode_plain(PacketKind::AttachReject, client_id, 0, 0, &body)?;
|
let out = protocol::encode_plain(PacketKind::AttachReject, client_id, 0, 0, &body)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1578,7 +1578,7 @@ async fn handle_resume(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
&body,
|
&body,
|
||||||
)?;
|
)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1690,7 +1690,7 @@ async fn handle_ping(
|
|||||||
SERVER_TO_CLIENT,
|
SERVER_TO_CLIENT,
|
||||||
b"",
|
b"",
|
||||||
)?;
|
)?;
|
||||||
socket.send_to(&out, peer).await?;
|
let _ = send_udp(socket, &out, peer).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1705,6 +1705,60 @@ fn remove_client(state: &Arc<Mutex<ServerState>>, client_id: [u8; 16]) {
|
|||||||
locked.remove_client_everywhere(&client_id);
|
locked.remove_client_everywhere(&client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_udp(socket: &UdpSocket, packet: &[u8], peer: SocketAddr) -> Result<bool> {
|
||||||
|
match socket.send_to(packet, peer).await {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(err) if is_transient_udp_send_error(&err) => Ok(false),
|
||||||
|
Err(err) => Err(err).with_context(|| format!("send UDP packet to {peer}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_transient_udp_send_error(err: &std::io::Error) -> bool {
|
||||||
|
matches!(
|
||||||
|
err.kind(),
|
||||||
|
std::io::ErrorKind::Interrupted
|
||||||
|
| std::io::ErrorKind::TimedOut
|
||||||
|
| std::io::ErrorKind::WouldBlock
|
||||||
|
) || err.raw_os_error().is_some_and(is_transient_udp_os_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn is_transient_udp_os_error(code: i32) -> bool {
|
||||||
|
matches!(
|
||||||
|
code,
|
||||||
|
libc::EADDRNOTAVAIL
|
||||||
|
| libc::ECONNREFUSED
|
||||||
|
| libc::ECONNRESET
|
||||||
|
| libc::EHOSTDOWN
|
||||||
|
| libc::EHOSTUNREACH
|
||||||
|
| libc::ENETDOWN
|
||||||
|
| libc::ENETRESET
|
||||||
|
| libc::ENETUNREACH
|
||||||
|
| libc::ETIMEDOUT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_transient_udp_os_error(code: i32) -> bool {
|
||||||
|
matches!(
|
||||||
|
code,
|
||||||
|
10049 // WSAEADDRNOTAVAIL
|
||||||
|
| 10050 // WSAENETDOWN
|
||||||
|
| 10051 // WSAENETUNREACH
|
||||||
|
| 10052 // WSAENETRESET
|
||||||
|
| 10054 // WSAECONNRESET
|
||||||
|
| 10060 // WSAETIMEDOUT
|
||||||
|
| 10061 // WSAECONNREFUSED
|
||||||
|
| 10064 // WSAEHOSTDOWN
|
||||||
|
| 10065 // WSAEHOSTUNREACH
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
fn is_transient_udp_os_error(_code: i32) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_ack(
|
async fn handle_ack(
|
||||||
state: &Arc<Mutex<ServerState>>,
|
state: &Arc<Mutex<ServerState>>,
|
||||||
peer: SocketAddr,
|
peer: SocketAddr,
|
||||||
@@ -3020,7 +3074,7 @@ async fn send_stream_open_to_client(
|
|||||||
found
|
found
|
||||||
};
|
};
|
||||||
if let Some((endpoint, packet)) = send {
|
if let Some((endpoint, packet)) = send {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3114,7 +3168,7 @@ async fn queue_or_send_stream_data_to_client(
|
|||||||
send
|
send
|
||||||
};
|
};
|
||||||
if let Some((endpoint, packet)) = send {
|
if let Some((endpoint, packet)) = send {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3189,7 +3243,7 @@ async fn flush_stream_pending_data_to_client(
|
|||||||
let Some((endpoint, packet)) = send else {
|
let Some((endpoint, packet)) = send else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3333,7 +3387,7 @@ async fn send_stream_packet_to_client(
|
|||||||
found
|
found
|
||||||
};
|
};
|
||||||
if let Some((endpoint, packet)) = send {
|
if let Some((endpoint, packet)) = send {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3435,7 +3489,7 @@ async fn broadcast_output(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (endpoint, packet) in sends {
|
for (endpoint, packet) in sends {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3488,7 +3542,7 @@ async fn broadcast_session_exit(
|
|||||||
persist::remove_runtime_dir(&sessions_dir, &session_name);
|
persist::remove_runtime_dir(&sessions_dir, &session_name);
|
||||||
}
|
}
|
||||||
for (endpoint, packet) in sends {
|
for (endpoint, packet) in sends {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3595,7 +3649,7 @@ async fn retransmit_pending(
|
|||||||
sends
|
sends
|
||||||
};
|
};
|
||||||
for (endpoint, packet) in sends {
|
for (endpoint, packet) in sends {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3691,7 +3745,7 @@ async fn maybe_rekey_clients(
|
|||||||
sends
|
sends
|
||||||
};
|
};
|
||||||
for (endpoint, packet) in sends {
|
for (endpoint, packet) in sends {
|
||||||
socket.send_to(&packet, endpoint).await?;
|
let _ = send_udp(socket, &packet, endpoint).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -4536,4 +4590,25 @@ mod tests {
|
|||||||
};
|
};
|
||||||
remote_bind_allowed(&config, "0.0.0.0").unwrap();
|
remote_bind_allowed(&config, "0.0.0.0").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn server_udp_send_classifier_treats_network_roaming_errors_as_transient() {
|
||||||
|
for code in [
|
||||||
|
libc::EADDRNOTAVAIL,
|
||||||
|
libc::ECONNREFUSED,
|
||||||
|
libc::ECONNRESET,
|
||||||
|
libc::EHOSTDOWN,
|
||||||
|
libc::EHOSTUNREACH,
|
||||||
|
libc::ENETDOWN,
|
||||||
|
libc::ENETRESET,
|
||||||
|
libc::ENETUNREACH,
|
||||||
|
libc::ETIMEDOUT,
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
is_transient_udp_send_error(&std::io::Error::from_raw_os_error(code)),
|
||||||
|
"expected errno {code} to be transient"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user