尝试连接到 Deribit 时,我的 c websocket 实现不起作用

问题描述 投票:0回答:1

主要出于有趣的目的,我编写了一个非常简单的 C 应用程序,它实现了一个尝试通过 SSL 与 Deribit 连接的 websocket。 应用程序与 Deribit 连接(工作正常),进行握手(工作正常),发送消息(可能工作),然后接收消息(不工作)。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PRINTERR() fprintf(stderr, "%s:L%i: error\n", __FILE__, __LINE__)

int main()
{
    /* connection*/
    const char *host = "www.deribit.com";
    const char *path = "/ws/api/v2";
    int port = 443;

    struct sockaddr_in server_addr;
    struct hostent *server;

    // Create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        PRINTERR();
        return -1;
    }

    // Configure server address
    server = gethostbyname(host);
    if (server == NULL) {
        PRINTERR();
        return -1;
    }

    bzero((char *) &server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
          (char *)&server_addr.sin_addr.s_addr,
          server->h_length);
    server_addr.sin_port = htons(port);

    // Connect to server
    if (connect(sockfd,(struct sockaddr *) &server_addr,sizeof(server_addr)) < 0) {
        PRINTERR();
        return -1;
    }

    // Initialize SSL context
    SSL_CTX *ssl_context;
    SSL *ssl;
    SSL_library_init();
    SSL_load_error_strings();
    ssl_context = SSL_CTX_new(TLS_client_method());
    if (!ssl_context) {
        PRINTERR();
        return -1;
    }

    SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL);

    // NOTE: assuming Ubuntu
    if (SSL_CTX_load_verify_locations(ssl_context, "/etc/ssl/certs/ca-certificates.crt", NULL) != 1) {
        PRINTERR();
        return -1;
    }


    // Connect SSL over the socket
    ssl = SSL_new(ssl_context);
    SSL_set_fd(ssl, sockfd);

    int ret = SSL_connect(ssl);
    if (ret != 1) {
        PRINTERR();
        return -1;
    } else {
        long ret = SSL_get_verify_result(ssl);
        if (ret != X509_V_OK) {
            PRINTERR();
            return -1;
        }
    }

    printf("connected to deribit (sockfd: %i)\n", sockfd);

    /* handshake */
    char request[1024];
    int request_len;
    char response[1024];
    int response_len;

    // Prepare the WebSocket handshake request
    request_len = sprintf(request, "GET %s HTTP/1.1\r\n"
                                   "Host: %s\r\n"
                                   "Upgrade: websocket\r\n"
                                   "Connection: Upgrade\r\n"
                                   "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                                   "Sec-WebSocket-Version: 13\r\n\r\n",
                                   path, host);
    // Send the WebSocket handshake request
    if (SSL_write(ssl, request, request_len) < 0) {
        PRINTERR();
        return -1;
    }

    // Receive the WebSocket handshake response
    response_len = SSL_read(ssl, response, sizeof(response));
    if (response_len < 0) {
        PRINTERR();
        return -1;
    }

    // Check if the connection has been upgraded to a WebSocket connection
    if (strstr(response, "HTTP/1.1 101 Switching Protocols") &&
        strstr(response, "upgrade: websocket") &&
        strstr(response, "Connection: upgrade")) {
            printf("WebSocket handshake successful\n");
    } else {
        printf("WebSocket handshake failed\n");
        return -1;
    }

    /* send message */
    char req[1024];
    snprintf(
        req,
        sizeof(req),
        "{\"jsonrpc\":\"2.0\",\"id\":8212,\"method\":\"public/test\",\"params\":{}}"
    );

    int message_length = strlen(req);
    int sent_bytes = 0;
    char frame[10 + message_length];
    int frame_length = 2 + message_length;

    // Set the frame header
    frame[0] = 0x81; // Fin + Text opcode
    if (message_length <= 125)
    {
        frame[1] = (char) message_length;
        frame_length = 2 + message_length;
    }
    else if (message_length <= 65535)
    {
        frame[1] = 126;
        frame[2] = (char) (message_length >> 8);
        frame[3] = (char) (message_length & 0xFF);
        frame_length = 4 + message_length;
    }
    else
    {
        frame[1] = 127;
        frame[2] = (char) (message_length >> 56);
        frame[3] = (char) (message_length >> 48);
        frame[4] = (char) (message_length >> 40);
        frame[5] = (char) (message_length >> 32);
        frame[6] = (char) (message_length >> 24);
        frame[7] = (char) (message_length >> 16);
        frame[8] = (char) (message_length >> 8);
        frame[9] = (char) (message_length & 0xFF);
        frame_length = 10 + message_length;
    }

    // Copy the message into the frame
    memcpy(frame + 2, req, message_length);

    // Send the frame over SSL
    sent_bytes = SSL_write(ssl, frame, frame_length);

    /* receive message */
    unsigned char buf[4096];
    unsigned char mask[4];
    int bytes_received;
    int payload_length;
    unsigned char frame_header[2];
    bytes_received = SSL_read(ssl, frame_header, 2);

    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }


    if (frame_header[0] == 0x88) {
        printf("connection closed by deribit\n");
    }

    payload_length = (frame_header[1] & 0x7F);
    if (payload_length == 126) {
        bytes_received = SSL_read(ssl, &payload_length, 2);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    } else if (payload_length == 127) {
        bytes_received = SSL_read(ssl, &payload_length, 8);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

   if (frame_header[1] & 0x80) {
        bytes_received = SSL_read(ssl, mask, 4);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

    bytes_received = SSL_read(ssl, buf, payload_length);
    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }
    if (frame_header[1] & 0x80) {
        //unmask_message(buf, mask, bytes_received);
    }

    printf("frame header received: [0]: %d, [1]: %d\n", frame_header[0], frame_header[1]);
    printf("payload_length: %i\n", (frame_header[1] & 0x7F));
    printf("mask:           %i\n", (frame_header[1] & 0x80));
    printf("buffer: %s\n", buf);

    return 0;
}

如果运行代码,您将看到在发送第一条消息(无论消息内容是什么)后,应用程序立即从 deribit 收到连接关闭消息(

frame_header[1] & 0x80
为 true)。 问题从何而来?从处理 SSL 开始?还是websocket实现有问题?

我尝试直接使用

sockfd
而不使用SSL。 我尝试检查我的证书与 Deribit 接受的证书之间是否存在潜在的不一致。 我尝试了不同的消息发送到 Deribit。

c websocket openssl client cryptocurrency
1个回答
0
投票

晚了 10 个月,但我刚刚在自己的一个项目中解决了这个确切的问题,并认为我会为未来的 Google 员工分享。

由于此应用程序充当 Deribit 服务器的“客户端”,因此它需要屏蔽其有效负载。根据 RFC 6455,这需要帧头中的 4 个字节(紧跟在长度字节之后)来存储掩码密钥,以及要设置的帧的第二个字节的 MSB。请注意,帧的第二个字节中仅使用 7 位来存储有效负载长度。第一个位 (MSB) 始终用于指示有效负载是否被屏蔽,以及是否期望帧头中再有 4 个字节作为屏蔽密钥。

然后必须使用 RFC 6455 中所述的掩码密钥对有效负载数据进行掩码

转换后数据的八位字节 i(“transformed-octet-i”)是 XOR 原始数据的八位字节 i(“original-octet-i”),八位字节位于索引处 i 对掩蔽密钥取模 4(“masking-key-octet-j”):

j = i MOD 4 变换后的八位字节-i = 原始八位字节-i XOR 掩码-key-octet-j

代码中可能的实现:

int _mask = 0x12345678;  // Should be randomly generated (securely)
char* mask_bytes = (char*)&_mask;
for (int i = 0; i < message_length; i++) {
    *((char*)frame + 6 + i) ^= mask_bytes[i % 4];  // frame + 6 is the beginning of the payload
}

我通过以下方式获得了与服务器正确通信的原始代码:

  1. 设置掩码位(
    frame[1] |= 0x80;
    )
  2. 在帧头中为
    mask
    添加 4 个字节,并将随机 4 字节值复制到其中
  3. 使用这个随机 4 字节值来屏蔽如上所述的有效负载(我粘贴了我使用的代码片段)

回顾:从客户端发送的帧必须被屏蔽,从服务器发送的帧不得被屏蔽。 OP忘记屏蔽从客户端发送的数据。

© www.soinside.com 2019 - 2024. All rights reserved.