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:
@@ -341,21 +341,6 @@ impl SftpHandler {
|
||||
let version = cursor.read_u32::<BigEndian>()?;
|
||||
info!("Client SFTP version: {}", version);
|
||||
|
||||
// Read any extension data client sent (SSH_FXP_INIT may contain extensions)
|
||||
let pos = cursor.position() as usize;
|
||||
let inner = cursor.get_ref();
|
||||
if inner.len() > pos && (inner.len() - pos) >= 4 {
|
||||
let ext_count = match cursor.read_u32::<BigEndian>() {
|
||||
Ok(n) => n,
|
||||
Err(_) => 0,
|
||||
};
|
||||
for i in 0..ext_count {
|
||||
let ext_name = read_sftp_string(&mut cursor).unwrap_or_default();
|
||||
let ext_data = read_sftp_string(&mut cursor).unwrap_or_default();
|
||||
debug!("Client extension[{}]: {} = {}", i, ext_name, ext_data);
|
||||
}
|
||||
}
|
||||
|
||||
let response = self.build_version_response(3)?;
|
||||
Ok(response)
|
||||
}
|
||||
@@ -376,8 +361,8 @@ impl SftpHandler {
|
||||
|
||||
let full_path = self.resolve_path(&path)?;
|
||||
|
||||
let file = if pflags & SftpFileFlags::SSH_FXF_READ != 0 {
|
||||
OpenOptions::new().read(true).open(&full_path).ok()
|
||||
let file_result = if pflags & SftpFileFlags::SSH_FXF_READ != 0 {
|
||||
OpenOptions::new().read(true).open(&full_path)
|
||||
} else if pflags & SftpFileFlags::SSH_FXF_WRITE != 0 {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.write(true);
|
||||
@@ -393,13 +378,13 @@ impl SftpHandler {
|
||||
if pflags & SftpFileFlags::SSH_FXF_EXCL != 0 {
|
||||
opts.create_new(true);
|
||||
}
|
||||
opts.open(&full_path).ok()
|
||||
opts.open(&full_path)
|
||||
} else {
|
||||
None
|
||||
return self.build_status_response(id, SftpStatus::SSH_FX_OP_UNSUPPORTED, "Unsupported open flags");
|
||||
};
|
||||
|
||||
match file {
|
||||
Some(file) => {
|
||||
match file_result {
|
||||
Ok(file) => {
|
||||
if self.handles.len() >= Self::MAX_HANDLES {
|
||||
warn!("SSH_FXP_OPEN: handle limit reached ({})", Self::MAX_HANDLES);
|
||||
return self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Handle limit reached");
|
||||
@@ -419,8 +404,8 @@ impl SftpHandler {
|
||||
|
||||
self.build_handle_response(id, &handle_id.to_be_bytes())
|
||||
}
|
||||
None => {
|
||||
self.build_status_response(id, SftpStatus::SSH_FX_FAILURE, "Failed to open file")
|
||||
Err(e) => {
|
||||
self.build_status_from_io_error(id, &e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1478,14 +1463,25 @@ impl SftpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建SSH_FXP_VERSION响应,包含扩展声明(参考OpenSSH sftp-server.c)
|
||||
/// 构建SSH_FXP_VERSION响应,包含扩展声明(参考OpenSSH sftp-server.c: process_init())
|
||||
///
|
||||
/// SFTP协议格式(draft-ietf-secsh-filexfer-02):
|
||||
/// uint32 length
|
||||
/// uint8 type (SSH_FXP_VERSION = 2)
|
||||
/// uint32 version
|
||||
/// // extensions: NO count field, simply paired strings until buffer empty
|
||||
/// string extension_name (= uint32(len_with_nul) + data + \0)
|
||||
/// string extension_data (= uint32(len_with_nul) + data + \0)
|
||||
///
|
||||
/// OpenSSH uses sshbuf_put_cstring() which includes NUL terminator.
|
||||
/// Client reads with sshbuf_get_cstring() which expects \0 at end.
|
||||
fn build_version_response(&self, version: u32) -> Result<Vec<u8>> {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
buffer.write_u8(SftpPacketType::SSH_FXP_VERSION as u8)?;
|
||||
buffer.write_u32::<BigEndian>(version)?;
|
||||
|
||||
// 扩展声明(OpenSSH sftp-server.c: process_init() 中声明支持的扩展)
|
||||
// 扩展声明 — OpenSSH sftp-server.c: process_init() style, NO count field
|
||||
let extensions: &[(&str, &str)] = &[
|
||||
("posix-rename@openssh.com", "1"),
|
||||
("hardlink@openssh.com", "1"),
|
||||
@@ -1498,13 +1494,14 @@ impl SftpHandler {
|
||||
("sha384-hash@openssh.com", "1"),
|
||||
("sha512-hash@openssh.com", "1"),
|
||||
];
|
||||
|
||||
buffer.write_u32::<BigEndian>(extensions.len() as u32)?;
|
||||
for (name, data) in extensions {
|
||||
buffer.write_u32::<BigEndian>(name.len() as u32)?;
|
||||
// sshbuf_put_cstring(buf, s) → sshbuf_put_string(buf, s, strlen(s)+1)
|
||||
buffer.write_u32::<BigEndian>((name.len() + 1) as u32)?;
|
||||
buffer.write_all(name.as_bytes())?;
|
||||
buffer.write_u32::<BigEndian>(data.len() as u32)?;
|
||||
buffer.write_u8(0)?;
|
||||
buffer.write_u32::<BigEndian>((data.len() + 1) as u32)?;
|
||||
buffer.write_all(data.as_bytes())?;
|
||||
buffer.write_u8(0)?;
|
||||
}
|
||||
|
||||
self.wrap_sftp_packet(&buffer)
|
||||
|
||||
Reference in New Issue
Block a user