SFTP 消息实际长度与标头长度不同

问题描述 投票:0回答:1

我有一个用 C# 编写的 SFTP 服务器。

有 SSH 层,然后是包含 SFTP 消息的“ssh 数据”字段。

当我将消息(SSH 数据)移动到 SFTP 层时,有基本的标头。

  1. 消息长度
  2. 消息类型
  3. 请求ID

在我收到的一些消息中,实际长度与标头中的长度(预期长度)不同。

发生这种情况时,大多数情况下实际长度小于预期长度。

发生这种情况我该怎么办?

  1. 将消息与下一个数据包聚合
  2. 断开客户端连接
  3. 其他选择

以及如果实际长度大于预期长度该怎么办。

SFTP层代码示例:

public void OnInput(byte[] data)
{
    DataWorker reader = new DataWorker(data);             

    if (reader.DataAvailable >= 5)
    {
        try
        {
            var msglength = reader.ReadUInt32();
 
            if (reader.DataAvailable != msglength)
            {
                //DoSome
            }

            var msgtype = (SftpMessageType)(int)reader.ReadByte();

            switch (msgtype)
            {
                case SftpMessageType.SSH_FXP_INIT:
                    _logger.Log(LogLevel.Information, "FXP_INIT");
                    HandleInit(reader);
                    break;

                //etc..
            }
        }
        catch (Exception e)
        {
            _channel?.SendClose(1);
            return;
        }
    }
    else
    {
        _logger.Log(LogLevel.Error, $"no data avialble. data length is: {reader.DataAvailable}.");
    }
}

我尝试聚合消息,大多数情况下都有效。

但是我不知道这是否是正确的解决方案,而且我也不知道如果实际长度大于预期长度该怎么办。

c# ssh sftp
1个回答
2
投票

什么是“实际长度”?哪有这回事。这个(和任何)协议定义了长度,这是最重要的。您遇到的唯一其他长度是当前加载的数据包的长度。

通常应用协议是通过某种传输协议实现的。在这种情况下,我们考虑可靠的流协议,即 TCP 或 TLS。这种传输协议有“读”和“写”操作。

那么客户端和服务器之间是如何通信的呢?客户端构造消息 X 并对该消息调用“write”。但是服务器如何读取它呢?天真的方法是简单地调用“read”并处理它。但这是错误的,因为实际上存在三种可能性:

  1. “读取”操作返回的正是客户端发送的消息。
  2. “读取”操作仅返回一条先前发送的消息。这可能而且确实会发生,因为碎片可能发生在客户端和服务器之间的任何一跳。
  3. “读取”操作返回了多条消息。不一定是 2 条消息,比如说 1.3 条也是可能的。 5 条消息也是如此。这种情况可能而且确实会发生,特别是当多条消息连续快速发送时。底层 TCP 堆栈可能会将多个小数据包连接成一个大数据包,请参见例如Nagle 算法

因此,当您编写流代码时,您需要处理上述所有内容。事实上,您的代码不会执行此操作。您只实现了第 1 点,这就是您看到这些不一致的原因。

发生这种情况我该怎么办?

妥善处理。给你具体的代码示例有点困难,因为我什至不知道你使用什么框架。无论哪种方式,通用算法可能如下所示:

while true:
    header = new byte[5]
    offset = 0
    while offset < 5:
        size = read(header, offset, 5-offset)
        offset += size

    length = get_length_from_header(header)
    flag = get_flag_from_header(header)

    # The next lines buffer entire message into memory.
    # Be aware that this is not efficient for file upload,
    # which should be streamed directly into FS instead. 
    message = new bytes[length]
    offset = 0
    while offset < length:
        size = read(message, offset, length - offset)
        offset += size

    process_message(message, length, flag)

此处

read
操作接受一个数组、应写入数组的偏移量以及应读取的字节数。它返回读取的实际字节数。所以是标准的低级读取操作。

当然这还可以进一步优化,例如一开始只读取5个字节并不一定有效。我们可以读取大缓冲区,比如 4k 字节,然后分析我们读取的内容。但它变得更复杂,所以让我们保持原样。

© www.soinside.com 2019 - 2024. All rights reserved.