简体   繁体   中英

Telnet Blocking C# TCP Server

I am writing a TCP server in C#, and have run into a strange and potentially a security issue as well.

My basic arch for accepting new connections is as follows:

  1. AC# Socket listening on a port, using the AcceptAsync method to accept incoming connections.
  2. Spinning off accepted connections using the ThreadPool for finishing the accept.

Everything works quite well, however everything grinds to a halt if someone telnets into the port.

Symptoms:

  • If I telnet into my server and do not send any data (ie do not hit any keys) the server will never finish accepting the connection.

  • My SocketAsyncEventArgs.Completed callback is never hit for the telnet connection.

  • Even worse, all further connections are blocked/queued and never get accepted by my code. They are put into a CLOSE_WAIT state:

    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

Any advice would be appreciated.

StartAccept:

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());
            }
        }
    }
}

Completed Callback for accepting connections:

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;
    }
}

Finish Accept:

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

Here is the wireshark from connecting telnet but before sending data:

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

This should be the complete handshake to establish my connection, but the Completed event is never raised.

Answering my own question, as I found the underlying cause:

The error is this line:

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

This is because if you set a buffer, AcceptAsync will block until it receives some data.

From MSDN :

The minimum buffer size required is 288 bytes. If a larger buffer size is specified, then the Socket will expect some extra data other than the address data received by the Winsock AcceptEx call and will wait until this extra data is received.

My corrected code:

// 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);

I'm not sure if this is the exact correct fix, setting a buffer of 288 bytes or smaller did not seem to correct the issue. Only setting the buffer to null caused the Completed event to be raised when connecting without sending data.

It looks like GetConnection is blocked somewhere. Usually if server is not under heavy load asynchronous operations may complete in synchronous manner. Also the fact that AcceptAsync returned false or callback method was called means that asynchronous operation is complete and your code should analyse the results.

Below is a simple asynchronous TCP server skeleton that accepts connections asynchronously.

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM