主要是为了好玩,我写了一个非常简单的c应用程序,它实现了一个WebSocket,试图通过SSL连接Deribit。应用程序连接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发送不同的消息。
1条答案
按热度按时间mw3dktmi1#
晚了10个月,但我刚刚在我自己的一个项目中解决了这个问题,并认为我会与未来的谷歌员工分享。
由于此应用程序充当Deribit服务器的“客户端”,因此它需要屏蔽其有效负载。(紧跟在长度字节之后)来存储掩码密钥,以及要设置的帧的第二字节的MSB。注意,帧的第二字节中只有7位用于存储有效载荷长度。第一位(MSB)始终用于指示有效载荷是否被屏蔽,以及是否期望在帧报头中为屏蔽键增加4个字节。
然后,必须使用RFC 6455中描述的掩码密钥对有效载荷数据进行 * 掩码 *:
变换数据的八位组i(“transformed-octet-i”)是原始数据的八位组i(“original-octet-i”)与掩码密钥的索引i处的八位组(“masking-key-octet-j”)模4的XOR:
j = i MOD 4 transformed-octet-i = original-octet-iXOR masking-key-octet-j
一个可能的代码实现:
字符串
我得到了与服务器正确通信的原始代码:
1.设置掩码位(
frame[1] |= 0x80;
)1.在帧头中为
mask
添加4个字节,并向其中复制一个随机的4字节值1.如上所述,使用这个随机的4字节值来屏蔽负载(我粘贴了我使用的代码片段)
回顾一下:从客户端发送的帧必须被屏蔽,从服务器端发送的帧不能被屏蔽。OP忘记屏蔽从客户端发送的数据。