Created
February 20, 2025 21:09
-
-
Save aneury1/8b7a496496a7861562ba5c3a9f089a5b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <sys/epoll.h> | |
#include <openssl/sha.h> | |
#include <openssl/bio.h> | |
#include <openssl/evp.h> | |
#include <openssl/buffer.h> | |
#define PORT 8081 | |
#define BUFFER_SIZE 1024 | |
#define MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | |
#define MAX_EVENTS 10 | |
#define MAX_CLIENTS 100 | |
// Client state | |
typedef struct { | |
int fd; // Client socket FD | |
char buffer[BUFFER_SIZE]; // Incoming data buffer | |
int buf_len; // Bytes in buffer | |
int is_websocket; // 0 = HTTP, 1 = WebSocket | |
} Client; | |
// Compute Sec-WebSocket-Accept | |
char* compute_accept_key(const char* client_key) { | |
char combined[100]; | |
snprintf(combined, sizeof(combined), "%s%s", client_key, MAGIC_STRING); | |
unsigned char sha1[SHA_DIGEST_LENGTH]; | |
SHA1((unsigned char*)combined, strlen(combined), sha1); | |
BIO* b64 = BIO_new(BIO_f_base64()); | |
BIO* mem = BIO_new(BIO_s_mem()); | |
b64 = BIO_push(b64, mem); | |
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); | |
BIO_write(b64, sha1, SHA_DIGEST_LENGTH); | |
BIO_flush(b64); | |
BUF_MEM* buffer; | |
BIO_get_mem_ptr(b64, &buffer); | |
char* accept_key = (char *)malloc(buffer->length + 1); | |
memcpy(accept_key, buffer->data, buffer->length); | |
accept_key[buffer->length] = '\0'; | |
BIO_free_all(b64); | |
return accept_key; | |
} | |
// Send WebSocket handshake response | |
void send_websocket_handshake(int client_fd, const char* client_key) { | |
char* accept_key = compute_accept_key(client_key); | |
char response[BUFFER_SIZE]; | |
snprintf(response, sizeof(response), | |
"HTTP/1.1 101 Switching Protocols\r\n" | |
"Upgrade: websocket\r\n" | |
"Connection: Upgrade\r\n" | |
"Sec-WebSocket-Accept: %s\r\n\r\n", | |
accept_key); | |
write(client_fd, response, strlen(response)); | |
free(accept_key); | |
} | |
// Send HTTP Hello World response | |
void send_http_response(int client_fd) { | |
const char* response = | |
"HTTP/1.1 200 OK\r\n" | |
"Content-Type: text/plain\r\n" | |
"Content-Length: 11\r\n" | |
"Connection: close\r\n\r\n" | |
"Hello World"; | |
write(client_fd, response, strlen(response)); | |
} | |
// Extract Sec-WebSocket-Key | |
char* extract_key(const char* request) { | |
const char* key_start = strstr(request, "Sec-WebSocket-Key: "); | |
if (!key_start) return NULL; | |
key_start += 19; | |
char* key_end = (char *)strstr(key_start, "\r\n"); | |
if (!key_end) return NULL; | |
int key_len = key_end - key_start; | |
char* key = (char *)malloc(key_len + 1); | |
strncpy(key, key_start, key_len); | |
key[key_len] = '\0'; | |
return key; | |
} | |
// Check if request is a WebSocket upgrade | |
int is_websocket_request(const char* request) { | |
return strstr(request, "Upgrade: websocket") != NULL && | |
strstr(request, "Sec-WebSocket-Key: ") != NULL; | |
} | |
// Decode masked payload | |
void decode_payload(char* payload, unsigned char* mask_key, int payload_len) { | |
for (int i = 0; i < payload_len; i++) { | |
payload[i] ^= mask_key[i % 4]; | |
} | |
} | |
// Set socket to non-blocking | |
void set_nonblocking(int fd) { | |
int flags = fcntl(fd, F_GETFL, 0); | |
fcntl(fd, F_SETFL, flags | O_NONBLOCK); | |
} | |
int main() { | |
int server_fd, epoll_fd; | |
struct sockaddr_in address; | |
int addrlen = sizeof(address); | |
Client clients[MAX_CLIENTS] = {0}; | |
// Create server socket | |
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { | |
perror("Socket failed"); | |
exit(EXIT_FAILURE); | |
} | |
set_nonblocking(server_fd); | |
// Set up address | |
address.sin_family = AF_INET; | |
address.sin_addr.s_addr = INADDR_ANY; | |
address.sin_port = htons(PORT); | |
// Bind and listen | |
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) { | |
perror("Bind failed"); | |
exit(EXIT_FAILURE); | |
} | |
if (listen(server_fd, SOMAXCONN) < 0) { | |
perror("Listen failed"); | |
exit(EXIT_FAILURE); | |
} | |
printf("Server listening on port %d\n", PORT); | |
// Create epoll instance | |
epoll_fd = epoll_create1(0); | |
if (epoll_fd == -1) { | |
perror("Epoll create failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Add server socket to epoll | |
struct epoll_event ev, events[MAX_EVENTS]; | |
ev.events = EPOLLIN; | |
ev.data.fd = server_fd; | |
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) { | |
perror("Epoll add server failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Main event loop | |
while (1) { | |
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); | |
if (nfds == -1) { | |
perror("Epoll wait failed"); | |
continue; | |
} | |
for (int i = 0; i < nfds; i++) { | |
int fd = events[i].data.fd; | |
if (fd == server_fd) { | |
// Accept new connection | |
int client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen); | |
if (client_fd == -1) { | |
perror("Accept failed"); | |
continue; | |
} | |
set_nonblocking(client_fd); | |
// Add client to epoll | |
ev.events = EPOLLIN | EPOLLET; | |
ev.data.fd = client_fd; | |
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) { | |
perror("Epoll add client failed"); | |
close(client_fd); | |
continue; | |
} | |
// Store client | |
for (int j = 0; j < MAX_CLIENTS; j++) { | |
if (clients[j].fd == 0) { | |
clients[j].fd = client_fd; | |
clients[j].is_websocket = 0; | |
clients[j].buf_len = 0; | |
break; | |
} | |
} | |
printf("New client connected: %d\n", client_fd); | |
} else { | |
// Find client | |
Client* client = NULL; | |
for (int j = 0; j < MAX_CLIENTS; j++) { | |
if (clients[j].fd == fd) { | |
client = &clients[j]; | |
break; | |
} | |
} | |
if (!client) continue; | |
// Read data | |
int bytes_read = read(fd, client->buffer + client->buf_len, BUFFER_SIZE - client->buf_len); | |
if (bytes_read <= 0) { | |
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); | |
close(fd); | |
memset(client, 0, sizeof(Client)); | |
printf("Client disconnected: %d\n", fd); | |
continue; | |
} | |
client->buf_len += bytes_read; | |
client->buffer[client->buf_len] = '\0'; // Null-terminate for string ops | |
if (!client->is_websocket) { | |
// Initial request: HTTP or WebSocket? | |
if (is_websocket_request(client->buffer)) { | |
char* client_key = extract_key(client->buffer); | |
if (client_key) { | |
send_websocket_handshake(fd, client_key); | |
client->is_websocket = 1; | |
client->buf_len = 0; | |
free(client_key); | |
printf("Client %d upgraded to WebSocket\n", fd); | |
} | |
} else { | |
// Assume HTTP, send Hello World and close | |
send_http_response(fd); | |
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); | |
close(fd); | |
memset(client, 0, sizeof(Client)); | |
printf("Served HTTP request to %d\n", fd); | |
} | |
} else { | |
// WebSocket frame handling | |
if (client->buf_len >= 2) { | |
unsigned char* buf = (unsigned char*)client->buffer; | |
int opcode = buf[0] & 0x0F; | |
int mask = (buf[1] & 0x80) >> 7; | |
int payload_len = buf[1] & 0x7F; | |
if (opcode == 0x1 && client->buf_len >= (2 + (mask ? 4 : 0) + payload_len)) { | |
char* payload = mask ? client->buffer + 6 : client->buffer + 2; | |
if (mask) { | |
unsigned char* mask_key = (unsigned char*)client->buffer + 2; | |
decode_payload(payload, mask_key, payload_len); | |
} | |
printf("Received from %d: %.*s\n", fd, payload_len, payload); | |
// Echo back | |
char frame[BUFFER_SIZE] = {(char)0x81, (char)payload_len}; | |
memcpy(frame + 2, payload, payload_len); | |
write(fd, frame, 2 + payload_len); | |
// Shift remaining data | |
int frame_len = 2 + (mask ? 4 : 0) + payload_len; | |
memmove(client->buffer, client->buffer + frame_len, client->buf_len - frame_len); | |
client->buf_len -= frame_len; | |
} | |
} | |
} | |
} | |
} | |
} | |
close(server_fd); | |
close(epoll_fd); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment