Telnet阻止C#TCP服务器

本文关键字:服务器 C#TCP 阻止 Telnet | 更新日期: 2023-09-27 18:20:24

我正在用C#编写一个TCP服务器,遇到了一个奇怪的、潜在的安全问题。

我接受新连接的基本架构如下:

  1. 监听端口的C#套接字,使用AcceptAsync方法接受传入连接
  2. 使用ThreadPool旋出已接受的连接以完成接受

一切都很好,但如果有人远程登录到端口,一切都会停止。

症状:

  • 如果我telnet到我的服务器,并且不发送任何数据(即,不点击任何键),服务器将永远无法完成接受连接。

  • 对于telnet连接,我的SocketAsyncEventArgs.Completed回调从未命中。

  • 更糟糕的是,所有进一步的连接都被阻塞/排队,并且永远不会被我的代码接受。它们被置于CLOSE_WAIT状态:

    TCP 127.0.0.1:8221 chance:53960 CLOSE_WAIT

    TCP 127.0.0.1:8221 chance:53962 CLOSE_WAIT

    TCP 127.0.0.1:8221 chance:53964 CLOSE_WAIT

如有任何建议,我们将不胜感激。

开始接受:

private void StartAccept(SocketAsyncEventArgs AcceptArgs)
{
    CurrentAcceptArgs = AcceptArgs;
    AcceptArgs.AcceptSocket = null;
    if (AcceptArgs.Buffer == null ||
        AcceptArgs.Buffer.Length < 1024)
    {
        AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
    }
    if (MainSocket != null)
    {
        lock (MainSocket)
        {
            // If this is false, we have an accept waiting right now, otherwise it will complete aynsc
            if (MainSocket.AcceptAsync(AcceptArgs) == false)
            {
                ThreadPool.QueueUserWorkItem(FinishAccept, AcceptArgs);
                StartAccept(GetConnection());
            }
        }
    }
}

已完成接受连接的回调:

protected override void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
    PWClientRemote RemoteClient = e.UserToken as PWClientRemote;
    // Determine which type of operation just completed and call the associated handler.
    switch (e.LastOperation)
    {
        case SocketAsyncOperation.Accept:
            StartAccept(GetConnection());
            ThreadPool.QueueUserWorkItem(FinishAccept, e);
            break;
        default:
            base.OnIOCompleted(sender, e);
            break;
    }
}

最终验收:

private void FinishAccept(object StateObject)
{
    SocketAsyncEventArgs args = (SocketAsyncEventArgs)StateObject;
    FinishAcceptInternal(args);
}

以下是连接telnet但在发送数据之前的wireshark:

No.     Time        Source                Destination           Protocol Length Info
  1 0.000000    192.168.1.146         192.168.1.109         TCP      66     59766 > 8221 [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
  2 0.000076    192.168.1.109         192.168.1.146         TCP      66     8221 > 59766 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
  3 0.000389    192.168.1.146         192.168.1.109         TCP      60     59766 > 8221 [ACK] Seq=1 Ack=1 Win=65536 Len=0

这应该是建立连接的完整握手,但从未引发Completed事件。

Telnet阻止C#TCP服务器

回答我自己的问题,因为我发现了根本原因:

错误是这一行:

if (AcceptArgs.Buffer == null ||
    AcceptArgs.Buffer.Length < 1024)
{
    AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
}

这是因为如果设置了缓冲区,AcceptAsync将阻塞,直到它接收到一些数据。

来自MSDN:

所需的最小缓冲区大小为288字节。如果指定了更大的缓冲区大小,那么Socket将期待一些额外的数据,而不是Winsock AcceptEx调用接收到的地址数据,并将等待,直到接收到这些额外的数据。

我更正的代码:

// We set a null buffer here.
// If we set a valid buffer, the accept will expect data
// and will hang unless it gets it.
AcceptArgs.SetBuffer(null, 0, 0);

我不确定这是否是正确的解决方案,设置288字节或更小的缓冲区似乎并不能解决这个问题。仅将缓冲区设置为null会导致在不发送数据的情况下进行连接时引发Completed事件。

看起来GetConnection在某个地方被阻止了。通常,若服务器不在重负载下,异步操作可以以同步方式完成。此外,AcceptAsync返回false或调用了回调方法,这意味着异步操作已经完成,您的代码应该分析结果。

下面是一个简单的异步TCP服务器框架,它接受异步连接。

void StartServer()
{
    Socket serverSocket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    serverSocket.Bind(new IPEndPoint(addr, port));
    s.Listen(5000);
    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptCompleted);
    args.UserToken = serverSocket;
    if ( !serverSocket.AcceptAsync(args) )
        AcceptCompleted(this, args);
}
void AcceptCompleted(object obj, SocketAsyncEventArgs args)
{
    Socket client = args.AcceptSocket;
    if (args.SocketError != SocketError.Success)
        return;
    StartClientOperations(args.AcceptSocket);
    args.AcceptSocket = null;
    Socket s = (Socket)args.UserToken;
    if (!s.AcceptAsync(args))
        AcceptCompleted(this, args);
}
void StartClientOperations(Socket newClient) 
{
    //start other asynchronous operations here with the client socket
}