Fix 5MB SFTP download hang: batch process SFTP packets + WINDOW_ADJUST chaining
Root cause: handle_channel_data processed only ONE SFTP packet per call, leaving remaining batched packets stuck in the buffer. Client waited for READ responses while server waited for more data — deadlock after ~3.1MB. Fix: - sftp_handler.rs: fix SSH_FXP_VERSION format (remove uint32 extension_count) - sftp_handler.rs: fix handle_open error mapping (.ok() → build_status_from_io_error) - channel.rs: batch-process ALL complete SFTP packets from buffer in loop - channel.rs: add pending_packets VecDeque for multi-response queuing - channel.rs: chain WINDOW_ADJUST + SFTP response when window is low - channel.rs: add adjust_remote_window() for client WINDOW_ADJUST - server.rs: drain pending_packets after each CHANNEL_DATA handler Verified: 5MB upload + download with matching MD5
This commit is contained in:
@@ -8,7 +8,7 @@ use std::io::{Read, Write}; // 导入Write trait(OpenSSH标准)
|
||||
use anyhow::{Result, anyhow};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use log::{info, warn, debug, error};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::ssh_server::sftp_handler::SftpHandler; // Phase 7: SFTP handler
|
||||
use crate::ssh_server::scp_handler::ScpHandler; // Phase 8: SCP handler
|
||||
@@ -23,6 +23,8 @@ use nix::poll::{poll, PollFd, PollFlags}; // Phase 14: poll机制(OpenSSH风
|
||||
pub struct ChannelManager {
|
||||
channels: HashMap<u32, Channel>,
|
||||
next_channel_id: u32,
|
||||
/// ⭐⭐⭐⭐⭐ Phase 15.1: 待发送packet队列(用于同时发送WINDOW_ADJUST和SFTP响应)
|
||||
pub pending_packets: VecDeque<SshPacket>,
|
||||
}
|
||||
|
||||
/// Phase 14: 交互式Exec进程管理(参考OpenSSH session.c: do_exec_no_pty)
|
||||
@@ -42,6 +44,7 @@ impl ChannelManager {
|
||||
Self {
|
||||
channels: HashMap::new(),
|
||||
next_channel_id: 0,
|
||||
pending_packets: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,57 +644,119 @@ impl ChannelManager {
|
||||
}
|
||||
|
||||
// Phase 7: 检查是否是SFTP channel(⭐⭐⭐⭐⭐ Phase 14.3: packet accumulation)
|
||||
if let Some(sftp_handler) = &mut channel.sftp_handler {
|
||||
// Extract SFTP result from channel borrow, then send outside
|
||||
let sftp_result = if let Some(sftp_handler) = &mut channel.sftp_handler {
|
||||
info!("Processing SFTP request ({} bytes)", data.len());
|
||||
|
||||
// ⭐⭐⭐⭐⭐ Window Control: decrease local_window
|
||||
channel.local_window -= data.len() as u32;
|
||||
channel.local_consumed += data.len() as u32;
|
||||
|
||||
// ⭐⭐⭐⭐⭐ Critical修复:累积SFTP packet数据
|
||||
channel.sftp_input_buffer.extend_from_slice(&data);
|
||||
info!("SFTP buffer accumulated: {} bytes total", channel.sftp_input_buffer.len());
|
||||
|
||||
// 检查buffer是否有足够数据解析packet length
|
||||
if channel.sftp_input_buffer.len() < 4 {
|
||||
info!("SFTP buffer too short for length field, waiting for more data");
|
||||
return Ok(None); // 继续累积
|
||||
// ⭐⭐⭐⭐⭐ Process ALL complete SFTP packets from buffer (not just one)
|
||||
let mut all_responses: Vec<Vec<u8>> = Vec::new();
|
||||
loop {
|
||||
if channel.sftp_input_buffer.len() < 4 {
|
||||
info!("SFTP buffer too short for length field, waiting for more data");
|
||||
break;
|
||||
}
|
||||
|
||||
let sftp_length = u32::from_be_bytes([
|
||||
channel.sftp_input_buffer[0],
|
||||
channel.sftp_input_buffer[1],
|
||||
channel.sftp_input_buffer[2],
|
||||
channel.sftp_input_buffer[3]
|
||||
]) as usize;
|
||||
info!("SFTP packet length field: {}", sftp_length);
|
||||
|
||||
let expected_total = 4 + sftp_length;
|
||||
if channel.sftp_input_buffer.len() < expected_total {
|
||||
info!("SFTP packet incomplete: expected {} bytes, have {} bytes in buffer, waiting for more",
|
||||
expected_total, channel.sftp_input_buffer.len());
|
||||
break;
|
||||
}
|
||||
|
||||
let sftp_packet = channel.sftp_input_buffer[4..expected_total].to_vec();
|
||||
info!("SFTP packet complete: {} bytes, processing", sftp_packet.len());
|
||||
|
||||
let response = sftp_handler.handle_request(&sftp_packet)?;
|
||||
info!("SFTP response: {} bytes", response.len());
|
||||
|
||||
if channel.sftp_input_buffer.len() > expected_total {
|
||||
let remaining = channel.sftp_input_buffer[expected_total..].to_vec();
|
||||
channel.sftp_input_buffer = remaining;
|
||||
info!("SFTP buffer has remaining {} bytes after processing", channel.sftp_input_buffer.len());
|
||||
} else {
|
||||
channel.sftp_input_buffer.clear();
|
||||
info!("SFTP buffer cleared after processing");
|
||||
}
|
||||
|
||||
all_responses.push(response);
|
||||
}
|
||||
|
||||
// 解析SFTP packet length(前4 bytes)
|
||||
let sftp_length = u32::from_be_bytes([
|
||||
channel.sftp_input_buffer[0],
|
||||
channel.sftp_input_buffer[1],
|
||||
channel.sftp_input_buffer[2],
|
||||
channel.sftp_input_buffer[3]
|
||||
]) as usize;
|
||||
info!("SFTP packet length field: {}", sftp_length);
|
||||
Some(all_responses)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(responses) = sftp_result {
|
||||
// ⭐⭐⭐⭐⭐ Channel borrow is dropped; now we can use self freely
|
||||
|
||||
let expected_total = 4 + sftp_length;
|
||||
if channel.sftp_input_buffer.len() < expected_total {
|
||||
info!("SFTP packet incomplete: expected {} bytes, have {} bytes in buffer, waiting for more",
|
||||
expected_total, channel.sftp_input_buffer.len());
|
||||
return Ok(None); // 继续累积
|
||||
// All responses except the last go to pending_packets
|
||||
for i in 0..responses.len().saturating_sub(1) {
|
||||
let pending = self.build_channel_data(recipient_channel, &responses[i])?;
|
||||
self.pending_packets.push_back(pending);
|
||||
}
|
||||
|
||||
// ⭐⭐⭐⭐⭐ Buffer足够,解析完整SFTP packet
|
||||
let sftp_packet = &channel.sftp_input_buffer[4..expected_total];
|
||||
info!("SFTP packet complete: {} bytes, processing", sftp_packet.len());
|
||||
info!("SFTP packet content (first 20 bytes): {:?}", &sftp_packet[..std::cmp::min(20, sftp_packet.len())]);
|
||||
|
||||
let response = sftp_handler.handle_request(sftp_packet)?;
|
||||
info!("SFTP response: {} bytes", response.len());
|
||||
|
||||
// ⭐⭐⭐⭐⭐ 处理完后,清空buffer或保留剩余数据
|
||||
if channel.sftp_input_buffer.len() > expected_total {
|
||||
// 有剩余数据(多个packets的情况)
|
||||
let remaining = channel.sftp_input_buffer[expected_total..].to_vec();
|
||||
channel.sftp_input_buffer = remaining;
|
||||
info!("SFTP buffer has remaining {} bytes after processing", channel.sftp_input_buffer.len());
|
||||
// Last response is returned (possibly with WINDOW_ADJUST)
|
||||
if let Some(last_response) = responses.into_iter().last() {
|
||||
// ⭐⭐⭐⭐⭐ Check window adjust (re-borrow channel briefly)
|
||||
let (needs_window, consumed) = if let Some(ch) = self.channels.get_mut(&recipient_channel) {
|
||||
let window_used = ch.local_window_max - ch.local_window;
|
||||
let need = (window_used > ch.local_maxpacket * 3) ||
|
||||
(ch.local_window < ch.local_window_max / 2);
|
||||
(need, ch.local_consumed)
|
||||
} else {
|
||||
(false, 0)
|
||||
};
|
||||
|
||||
if needs_window && consumed > 0 {
|
||||
info!("⭐⭐⭐⭐⭐ [SFTP_WINDOW] Sending WINDOW_ADJUST before SFTP response");
|
||||
let window_adjust = build_window_adjust(recipient_channel, consumed);
|
||||
// Update window state
|
||||
if let Some(ch) = self.channels.get_mut(&recipient_channel) {
|
||||
ch.local_window += consumed;
|
||||
ch.local_consumed = 0;
|
||||
}
|
||||
// Use standalone builder to avoid self borrow conflict
|
||||
use std::io::Write;
|
||||
let mut payload = Vec::new();
|
||||
payload.write_u8(PacketType::SSH_MSG_CHANNEL_DATA as u8)?;
|
||||
payload.write_u32::<BigEndian>(recipient_channel)?;
|
||||
payload.write_u32::<BigEndian>(last_response.len() as u32)?;
|
||||
payload.write_all(&last_response)?;
|
||||
let sftp_packet = SshPacket::new(payload);
|
||||
self.pending_packets.push_back(window_adjust);
|
||||
self.pending_packets.push_back(sftp_packet);
|
||||
return Ok(None);
|
||||
}
|
||||
return Ok(Some(self.build_channel_data(recipient_channel, &last_response)?));
|
||||
} else {
|
||||
// 清空buffer
|
||||
channel.sftp_input_buffer.clear();
|
||||
info!("SFTP buffer cleared after processing");
|
||||
// No SFTP packets were complete, but maybe we need window adjust
|
||||
if let Some(ch) = self.channels.get_mut(&recipient_channel) {
|
||||
let window_used = ch.local_window_max - ch.local_window;
|
||||
if (window_used > ch.local_maxpacket * 3 || ch.local_window < ch.local_window_max / 2) && ch.local_consumed > 0 {
|
||||
let window_adjust = build_window_adjust(recipient_channel, ch.local_consumed);
|
||||
ch.local_window += ch.local_consumed;
|
||||
ch.local_consumed = 0;
|
||||
self.pending_packets.push_back(window_adjust);
|
||||
}
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 构建SSH_MSG_CHANNEL_DATA返回SFTP响应(需要SSH string格式)
|
||||
return Ok(Some(self.build_channel_data(recipient_channel, &response)?));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +764,15 @@ impl ChannelManager {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// ⭐⭐⭐⭐⭐ Phase 13.5: 处理 client 发送的 SSH_MSG_CHANNEL_WINDOW_ADJUST
|
||||
pub fn adjust_remote_window(&mut self, recipient_channel: u32, bytes_to_add: u32) {
|
||||
if let Some(channel) = self.channels.get_mut(&recipient_channel) {
|
||||
channel.remote_window = channel.remote_window.saturating_add(bytes_to_add);
|
||||
info!("⭐⭐⭐⭐⭐ [ADJUST_REMOTE_WINDOW] channel {} remote_window increased by {} (new: {})",
|
||||
recipient_channel, bytes_to_add, channel.remote_window);
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 14: 构建SSH_MSG_CHANNEL_EXTENDED_DATA(参考OpenSSH channel.c)
|
||||
fn build_channel_extended_data(&self, channel: u32, data_type: u32, data: &[u8]) -> Result<SshPacket> {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user