主要出于有趣的目的,我编写了一个非常简单的 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。
晚了 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
}
我通过以下方式获得了与服务器正确通信的原始代码:
frame[1] |= 0x80;
)mask
添加 4 个字节,并将随机 4 字节值复制到其中回顾:从客户端发送的帧必须被屏蔽,从服务器发送的帧不得被屏蔽。 OP忘记屏蔽从客户端发送的数据。