【问题标题】:Check if request comes over HTTP or HTTPS in socket listener检查请求是否来自套接字侦听器中的 HTTP 或 HTTPS
【发布时间】:2024-01-15 21:39:01
【问题描述】:

我有多线程异步套接字侦听器。我想检查请求是否安全。但我想在 AcceptCallBack 方法中检查,而不是在 ReceiveCallBack 中检查。

我会这样做是因为我希望我的代码同时适用于 HTTP 和 HTTPS。如果请求来自 HTTPS,我将继续使用经过身份验证的 SslStream 而不是原始套接字。

这是我的代码:

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

namespace LearnRequestType
{
    class *
    {
        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();
}

如果我像这样更改 AcceptCallBack 方法和 StateObject 类:

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

我可以决定传入的数据类型是 HTTP 还是 HTTPS,但如果是 HTTP,则每次都会由 catch 块处理,因此会降低应用程序性能。

还有其他方法吗?

【问题讨论】:

    标签: c# sockets http https


    【解决方案1】:

    如果我理解正确,您有一个端口,客户端可以使用 HTTP 或 HTTPS 进行连接,并且您想在传输任何数据之前立即知道请求是如何发出的。

    在您从客户端接收数据之前无法知道这一点。 HTTP 和 HTTPS 是 TCP 之上的协议,它们不能在较低的协议级别上工作,因此没有标志或任何可以说明使用哪种协议的东西。此外,HTTPS 只是包装在 TLS/SSL 流中的普通 HTTP 流。

    您必须读取数据并根据使用的协议确定。或者,您必须为 HTTP 和 HTTPS 设置单独的端口,这会使这变得微不足道。

    要检测它是否是 TLS/SSL,您可以窥视几个字节,看看里面有什么。The TLS specificationClient Hello 数据包以协议版本开头,它作为两个uint8s 发送。由于 HTTP 请求总是将动词作为第一个,因此您可以轻松检查前几个字节是否为字符,如果不是,请尝试 SSLStream

    另请注意,如果您在套接字上启动SSLStream,它可能会从套接字读取,这会消耗 HTTP 请求的开头,您不能像往常一样处理它。

    所以在您的 Accept 回调中使用如下内容:

    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
    }
    

    如果您想真正确定它是 TLS/SSL,您可以查看this question on SO

    【讨论】:

    • 感谢您的回答。实际上,我可以通过这样做来检查它是 HTTP 还是 HTTPS。检查我编辑的问题。但这里的问题是 try catch 块。
    • @OrkunBekar 添加了有关如何很好地猜测它是否是 HTTP 的信息。
    • 如何查看协议版本?它是否存储在 AcceptCallBack 方法中的对象中?我将在哪里获得这两个 uint8 值?
    • @OrkunBekar 您必须使用Socket.Receive() 自己读取数据,使用SocketFlags.Peek 确保不会中断实际数据流。它们应该是从套接字传入的第一个字节。
    • 感谢您给出正确答案。我对其进行了测试,每次我了解请求是来自 HTTP 还是 HTTPS。但是你写了“最有可能”和“应该”。这是否意味着有时我会得到错误的信息?
    最近更新 更多