Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if request comes over HTTP or HTTPS in socket listener

I have multi threaded async socket listener. I want to check if the request comes as secure or not. But I want to check that in AcceptCallBack method not ReceiveCallBack.

I will do that because I want my code to work for both HTTP and HTTPS. If request comes from HTTPS I will just go on with an authenticated SslStream instead of raw socket.

Here is my code:

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;

namespace LearnRequestType
{
    class StackOverFlow
    {
        private static readonly ManualResetEvent _manualResetEvent = new ManualResetEvent(false);

        private void StartListening()
        {
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 9002);

            if (localEndPoint != null)
            {
                Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                if (listener != null)
                {
                    listener.Bind(localEndPoint);
                    listener.Listen(10);

                    Console.WriteLine("Socket listener is running...");

                    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                }
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            _manualResetEvent.Set();

            Socket listener = (Socket)ar.AsyncState;

            Socket handler = listener.EndAccept(ar);

            StateObject state = new StateObject();
            state.workSocket = handler;

            // I want to understand if request comes from HTTP or HTTPS before this line.
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);

            listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
        }

        private void ReceiveCallback(IAsyncResult result)
        {
            StateObject state = (StateObject)result.AsyncState;
            Socket handler = state.workSocket;

            string clientIP = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();

            int numBytesReceived = handler.EndReceive(result);

            if (!handler.Connected)
            {
                handler.Close();
                return;
            }

            // Read incoming data...
            if (numBytesReceived > 0)
            {
                state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, numBytesReceived));

                // Read incoming data line by line.
                string[] lines = state.sb.ToString().Split('\n');

                if (lines[lines.Length - 1] == "<EOF>")
                {
                    // We received all data. Do something...

                }
                else
                {
                    // We didn't receive all data. Continue reading...
                    handler.BeginReceive(state.buffer, 0, state.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
                }
            }
        }
    }
}

public class StateObject
{
    public Socket workSocket = null;
    public const int BufferSize = 256;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
}

If I change AcceptCallBack method and StateObject Class like that:

private void AcceptCallback(IAsyncResult ar)
{
    _manualResetEvent.Set();

    Socket listener = (Socket)ar.AsyncState;

    Socket handler = listener.EndAccept(ar);

    try
    {
        sslStream = new SslStream(new NetworkStream(handler, true));

        // try to authenticate
        sslStream.AuthenticateAsServer(_cert, false, System.Security.Authentication.SslProtocols.Tls, true);

        state.workStream = sslStream;
        state.workStream.ReadTimeout = 100000;
        state.workStream.WriteTimeout = 100000;

        if (state.workStream.IsAuthenticated)
        {
            state.workStream.BeginRead(state.buffer, 0, StateObject.BufferSize, ReceiveCallback, state);
        }
    }
    catch (IOException ex)
    {
        // ıf we get handshake failed due to an unexpected packet format, this means incoming data is not HTTPS
        // Continue with socket not sslstream
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
    }

    StateObject state = new StateObject();
    state.workStream = handler;

    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
}

public class StateObject
{
    public Socket workSocket = null;
    public SslStream workStream = null;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
}

I can decide if incoming data type is HTTP or HTTPS but if it is HTTP it will be handled by catch block every time so it will decrease application performance.

Is there another way?

like image 258
Orkun Bekar Avatar asked May 20 '15 07:05

Orkun Bekar


1 Answers

If I understood correctly, you have a single port where the client may use HTTP or HTTPS to connect and you want to immediately know how the request was made, before any data is transferred.

There is no way of knowing this before you receive data from the client. HTTP and HTTPS are protocols on top of TCP, they do not work on lower protocol levels, so there is no flag or anything that could say which protocol is used. Also HTTPS is just the normal HTTP stream wrapped in a TLS/SSL stream.

You will have to read data and determine based on that which protocol is used. Or you have to have separate ports for HTTP and HTTPS, which would make this trivial.

To detect if it is TLS/SSL you could peek a few bytes and see what comes in. The TLS specification says the Client Hello packet starts with protocol version, which is sent as two uint8s. Since a HTTP request will always have the verb as first, you could easily check if a couple of the first bytes are characters or not and then try SSLStream if it is not.

Also note that if you initiate SSLStream on the socket, it might read from the socket, which would consume the beginning of the HTTP request and you couldn't just handle it as normally.

So in your Accept callback use something like this:

Socket handler = listener.EndAccept(ar);
byte[] tmp = new byte[2];
handler.Receive(tmp, 0, 2, SocketFlags.Peek);
if (!Char.IsLetter((char)tmp[0]) || !Char.IsLetter((char)tmp[1]))
{
  // Doesn't start with letters, so most likely not HTTP
} else {
  // Starts with letters, should be HTTP
}

If you would like to actually make sure it is TLS/SSL, you could check this question on SO

like image 100
Sami Kuhmonen Avatar answered Sep 29 '22 07:09

Sami Kuhmonen