отправка фрейма c ++ websocket заставляет сервер закрывать соединение - PullRequest
0 голосов
/ 08 октября 2019

У меня проблемы с созданием клиента / сервера websocket на c ++. Я учусь на исходном коде poco и ссылках на протоколы здесь: https://tools.ietf.org/html/rfc6455#section-1.3

Я мог бы использовать веб-сокеты poco, но я хочу создать асинхронные веб-сокеты, основанные на моей реализации асинхронного сокета

весь мой код написан только для Windows, и я тестирую с протоколом отладки Chrome, найденным здесь: https://chromedevtools.github.io/devtools-protocol/tot

Сначала я подключаюсь к серверу, используя эту функцию:

bool LIB_NAMESPACE::net::WebSocket::Connect(string_view Host, uint16_t Port, string_view Path, string_view Origin)
{
    Dns dns;
    if (!dns.Resolve(Host))
        return false;

    SockAddr *AddrPtr;

    for (auto& Addr : dns.AddrsInfo) // std::variant<IpV4Addr, IpV6Addr>
    {
        if (Addr.Addrs.index() == 0)
            AddrPtr = static_cast<SockAddr*>(&std::get<0>(Addr.Addrs));
        else if (Addr.Addrs.index() == 1)
            AddrPtr = static_cast<SockAddr*>(&std::get<1>(Addr.Addrs));
        else
            continue;
        AddrPtr->port = Port;
        if (Connect(*AddrPtr, Path, Host, Origin))
            return true;
    }
    return false;
}

bool LIB_NAMESPACE::net::WebSocket::Connect(const SockAddr & addr, string_view Path, string_view host, string_view Origin)
{
    sock.Close();

    Socket ConnectorSock{ addr.Family(), SocketType::TcpStream, ProtocolType::Tcp };
    if (!ConnectorSock)
        return false;

    if (!ConnectorSock.Connect(addr))
        return false;


    std::string RequesBuffer =
        "GET " + static_cast<std::string>(Path) + " HTTP/1.1\r\n";
    RequesBuffer += "Host: " + (!host.empty() ? static_cast<std::string>(host) : addr.IpStr());
    if (addr.port == 80)
        RequesBuffer += "\r\n";
    else
        RequesBuffer += ':' + std::to_string(addr.port) + "\r\n";
    RequesBuffer +=
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
        "Sec-WebSocket-Version: 13\r\n\r\n";

    auto Sent = ConnectorSock.Send(RequesBuffer.c_str(), RequesBuffer.size());
    if (Sent <= 0 || Sent != RequesBuffer.size())
        return false;

    string Response;
    char chunk[100];
    while (!Response.ends_with("\r\n\r\n"))
    {
        auto Recved = ConnectorSock.Recv(chunk, 100);
        if (Recved <= 0)
            return false;
        Response.append(chunk, chunk + Recved);
    }

    std::cout << Response << std::endl;
    /*
    HTTP/1.1 101 WebSocket Protocol Handshake
    Upgrade: WebSocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    */

    // needs better http parsing

    if (!Response.icontains("HTTP/1.1 101") || !Response.icontains("Upgrade: websocket")
        || !Response.icontains("Connection: Upgrade") || !Response.icontains("Sec-WebSocket-Accept"))
        return false;

    sock = std::move(ConnectorSock);
    return true;
}

Соединение открывается, однако мне не хватает проверки Sec-WebSocket-Accept, но я думаю, что сейчас это нормально и может быть реализовано позже

, тогда я объявляю эти структуры для отправки данных на сервер:

class big_endian_u16
{
        uint16_t big_endian_val;
    public:

        big_endian_u16() : big_endian_val(0) {}

        big_endian_u16(uint16_t val) : big_endian_val(_byteswap_ushort(val)) {}

        operator uint16_t() const { return _byteswap_ushort(big_endian_val); }

};

class big_endian_u32
{
        uint32_t big_endian_val;
    public:

        big_endian_u32() : big_endian_val(0) {}

        big_endian_u32(uint32_t val) : big_endian_val(_byteswap_ulong(val)) {}

        operator uint32_t() const { return _byteswap_ulong(big_endian_val); }

};

class big_endian_u64
{
        uint64_t big_endian_val;
    public:

        big_endian_u64() : big_endian_val(0) {}

        big_endian_u64(uint64_t val) : big_endian_val(_byteswap_uint64(val)) {}

        operator uint64_t() const { return _byteswap_uint64(big_endian_val); }

};

struct WebSocketHeaderRaw
{
        bool FinalFragment : 1;
        char RSV1 : 1;
        char RSV2 : 1;
        char RSV3 : 1;
        WebSocketFrame OpCode : 4;
        bool IsMasked : 1;
        unsigned char PayloadLength : 7;

        WebSocketHeaderRaw()
        {
        static_assert(sizeof(WebSocketHeaderRaw) == 2, "invalid size of WebSocketHeaderRaw");
        }

};

enum class WebSocketFrame : unsigned char
{
   Continuation = 0X0,
   Text= 0x1,
   Binary = 0x2,
   Close = 0x8,
   Ping = 0x9,
   Pong = 0xA,
};

constexpr int MaxPayloadSize = std::numeric_limits<int>::max();

inline void MaskUnmask(char *Buffer, const char *UnMaskedBuffer, int Length, 
char Mask[4])
{
   for (auto i : range(0, Length))
       Buffer[i] = UnMaskedBuffer[i] ^ Mask[i % 4];
}

и это метод отправки:

int LIB_NAMESPACE::net::WebSocket::SendFrame(WebSocketFrame FrameType, const char * Buffer, int Length)
{
    WebSocketHeaderRaw RawHeader;
    RawHeader.FinalFragment = true;
    RawHeader.RSV1 = 0;
    RawHeader.RSV2 = 0;
    RawHeader.RSV3 = 0;
    RawHeader.IsMasked = MustMask; // this is true because I'm using the websocket as client
    RawHeader.OpCode = FrameType; // I send only text frames -> 0x1
    big_endian_u16 u16Length;
    big_endian_u64 u64Length;
    char Mask[4];

    uint32_t u32Length = static_cast<uint32_t>(Length);

    if (u32Length < 126)
        RawHeader.PayloadLength = static_cast<unsigned char>(u32Length);

    else if (u32Length < std::numeric_limits<uint16_t>::max())
    {
        RawHeader.PayloadLength = 126;
        u16Length = static_cast<uint16_t>(u32Length);
    }
    else
    {
        RawHeader.PayloadLength = 127;
        u64Length = static_cast<uint64_t>(u32Length);
    }

    std::vector<char> MaskedBuffer;
    if (MustMask)
    {
        *(uint32_t*)Mask = RandomULongLong(std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint64_t>::max() - 1); // uses c++ random library
        MaskedBuffer.resize(Length);
        MaskUnmask(MaskedBuffer.data(), Buffer, Length, Mask);
        Buffer = MaskedBuffer.data();
    }

    int Sent = sock.Send((char*)&RawHeader, sizeof(RawHeader));
    if (Sent <= 0)
        return Sent;

    if (u16Length)
    {
        Sent = sock.Send((char*)&u16Length, sizeof(u16Length));
        if (Sent <= 0)
            return Sent;
    }

    else if (u64Length)
    {
        Sent = sock.Send((char*)&u16Length, sizeof(u64Length));
        if (Sent <= 0)
            return Sent;
    }

    if (MustMask)
    {
        Sent = sock.Send(Mask, sizeof(Mask));
        if (Sent <= 0)
            return Sent;
    }

    return sock.Send(Buffer, Length);
}

после того, как я что-либо отправляю на сервер, он закрывает соединение, и когда я начинаю получать, recv возвращает 0, потому что сервер закрыл соединение

операция получения завершается неудачно при попытке получить заголовки

LIB_NAMESPACE::net::WebSocketHeader LIB_NAMESPACE::net::WebSocket::RecvHeader()
{
    WebSocketHeaderRaw RawHeader;
    WebSocketHeader header{ 0 };

    int Recved = sock.RecvAll((char*)&RawHeader, sizeof(RawHeader));

    if (Recved <= 0) // --> it fails here
    {
        std::cout << "[!] RecvAll (header) failed with error " << WSAGetLastError() << std::endl;
        return header;
    }

    char LengthBuffer[sizeof(uint64_t)];

    if (RawHeader.PayloadLength == 127)
    {
        Recved = sock.RecvAll(LengthBuffer, sizeof(uint64_t));
        if (Recved <= 0)
        {
            std::cout << "[!] RecvAll (length 127) failed with error " << WSAGetLastError() << std::endl;
            return header;
        }
        uint64_t Length = _byteswap_uint64(*(uint64_t*)LengthBuffer);
        if (Length < MaxPayloadSize)
            header.PayloadLength = static_cast<int>(Length);
    }

    else if (RawHeader.PayloadLength == 126)
    {
        Recved = sock.RecvAll(LengthBuffer, sizeof(uint16_t));
        if (Recved <= 0)
        {
            std::cout << "[!] RecvAll (length 126) failed with error " << WSAGetLastError() << std::endl;
            return header;
        }
        uint16_t Length = _byteswap_ushort(*(uint16_t*)LengthBuffer);
        if (Length < MaxPayloadSize)
            header.PayloadLength = static_cast<int>(Length);
    }

    else if (RawHeader.PayloadLength < MaxPayloadSize)
        header.PayloadLength = static_cast<int>(RawHeader.PayloadLength);

    if (!header.PayloadLength)
    {
        std::cout << "[!] PayloadLength is 0 " << std::endl;
        return header;
    }

    if (RawHeader.IsMasked)
    {
        Recved = sock.RecvAll(header.MaskKey, sizeof(header.MaskKey));
        if (Recved <= 0)
        {
            std::cout << "[!] RecvAll (Mask) failed with error " << WSAGetLastError() << std::endl;
            header.PayloadLength = 0;
            return header;
        }
    }

    header.FinalFragment = RawHeader.FinalFragment;
    header.IsMasked = RawHeader.IsMasked;
    header.OpCode = RawHeader.OpCode;
    header.extensions.RSV1 = RawHeader.RSV1;
    header.extensions.RSV2 = RawHeader.RSV2;
    header.extensions.RSV3 = RawHeader.RSV3;

    return header;

}

это мое использование:

int wmain(int argc, wchar_t **argv)
{

    std::string wsPath; // chrome path of webSocketDebuggerUrl
    cout << "enter the path : ";
    cin >> wsPath;
    rad::net::WebSocket ws;
    if (!ws.Connect("localhost", 9222, std::string_view(wsPath)))
        cout << "[!] failed to connect" << endl;
    else
        cout << "[*] connected successfully" << endl;
    if (ws.SendTextFrame(R"({"id": 1, "method": "Network.enable" })") <= 0) // this passes but the server closes the connection
    {
        cout << "[!] failed to send the frame !" << endl;
        getchar();
        getchar();
        return 0;
    }
    while (1)
    {
        std::vector<char> Buffer;
        if (ws.RecvTextFrame(Buffer) <= 0) // always fails here
            break;
        std::cout << ">> " << string_view(Buffer.data(), Buffer.size()) << std::endl;
    }
    cout << "[!] failed to receive the frame !" << endl;
    getchar();
    getchar();
    return 0;
}

любой онЛП приветствуется

1 Ответ

0 голосов
/ 10 октября 2019

Битовые поля были упорядочены в памяти от низкого к высокому, но биты в пакете заголовка были упорядочены от высокого к низкому

Этот порядок битовых полей гарантируется в msvc согласно этой ссылке: https://docs.microsoft.com/en-us/cpp/cpp/cpp-bit-fields?view=vs-2019

Я пытался с gcc, и он, кажется, имеет то же поведение

Поэтому мне пришлось повернуть все байтовые компоненты в структуре, чтобы получить правильный результат

Я также попытался установитьи извлечение битов с помощью побитовых операторов, и это работало также как битовые поля, но битовые поля более элегантны для меня

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...