Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created June 21, 2025 18:56
Show Gist options
  • Save masakielastic/f346d29b31fbcaeab84af0be8c47714a to your computer and use it in GitHub Desktop.
Save masakielastic/f346d29b31fbcaeab84af0be8c47714a to your computer and use it in GitHub Desktop.
select システムコールで HTTP/1 + TLS サーバー

select システムコールで HTTP/1 + TLS サーバー

ビルドしてサーバーを起動させます。

gcc -o server server.c -lssl -lcrypto
./server

curl で HTTP リクエストを送信します。

curl -k -v https://localhost:8443
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#define PORT 8443
#define MAX_CLIENTS 10
#define BUFFER_SIZE 4096
// クライアント接続情報
typedef struct {
int fd;
SSL *ssl;
int tls_established;
} client_info_t;
// HTTPレスポンスのテンプレート
const char *http_response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"Content-Length: 147\r\n"
"\r\n"
"<html><body>"
"<h1>Hello from C HTTPS Server!</h1>"
"<p>This server uses select() with TLS/SSL encryption.</p>"
"</body></html>";
// グローバル変数
SSL_CTX *ssl_ctx = NULL;
client_info_t clients[MAX_CLIENTS];
// OpenSSLの初期化
int init_openssl() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
return 1;
}
// OpenSSLのクリーンアップ
void cleanup_openssl() {
EVP_cleanup();
ERR_free_strings();
}
// 自己署名証明書とプライベートキーを生成
int generate_certificate() {
EVP_PKEY *pkey = NULL;
X509 *x509 = NULL;
FILE *fp = NULL;
int ret = 0;
// RSAキーペアを生成
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) goto cleanup;
if (EVP_PKEY_keygen_init(ctx) <= 0) goto cleanup;
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) goto cleanup;
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) goto cleanup;
// X.509証明書を作成
x509 = X509_new();
if (!x509) goto cleanup;
// 証明書の設定
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // 1年間有効
X509_set_pubkey(x509, pkey);
// 証明書の名前を設定
X509_NAME *name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"JP", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"Test", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0);
X509_set_issuer_name(x509, name);
// 証明書に署名
if (!X509_sign(x509, pkey, EVP_sha256())) goto cleanup;
// プライベートキーをファイルに保存
fp = fopen("server.key", "wb");
if (!fp) goto cleanup;
PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL);
fclose(fp);
fp = NULL;
// 証明書をファイルに保存
fp = fopen("server.crt", "wb");
if (!fp) goto cleanup;
PEM_write_X509(fp, x509);
fclose(fp);
fp = NULL;
printf("Generated self-signed certificate: server.crt and server.key\n");
ret = 1;
cleanup:
if (fp) fclose(fp);
if (ctx) EVP_PKEY_CTX_free(ctx);
if (pkey) EVP_PKEY_free(pkey);
if (x509) X509_free(x509);
return ret;
}
// SSL contextの作成
SSL_CTX *create_ssl_context() {
const SSL_METHOD *method;
SSL_CTX *ctx;
method = TLS_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
ERR_print_errors_fp(stderr);
return NULL;
}
// セキュリティオプションを設定
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
return ctx;
}
// 証明書とキーを読み込み
int configure_ssl_context(SSL_CTX *ctx) {
// 証明書ファイルが存在しない場合は生成
if (access("server.crt", F_OK) != 0 || access("server.key", F_OK) != 0) {
if (!generate_certificate()) {
fprintf(stderr, "Failed to generate certificate\n");
return 0;
}
}
// 証明書を読み込み
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
// プライベートキーを読み込み
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
// プライベートキーが証明書と一致するかチェック
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match certificate\n");
return 0;
}
return 1;
}
// ソケットを非ブロッキングモードに設定
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// クライアント情報を初期化
void init_clients() {
for (int i = 0; i < MAX_CLIENTS; i++) {
clients[i].fd = -1;
clients[i].ssl = NULL;
clients[i].tls_established = 0;
}
}
// 空いているクライアントスロットを見つける
int find_free_client_slot() {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd == -1) {
return i;
}
}
return -1;
}
// ファイルディスクリプタからクライアントスロットを見つける
int find_client_by_fd(int fd) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd == fd) {
return i;
}
}
return -1;
}
// クライアントを削除
void remove_client(int slot) {
if (slot < 0 || slot >= MAX_CLIENTS) return;
if (clients[slot].ssl) {
SSL_shutdown(clients[slot].ssl);
SSL_free(clients[slot].ssl);
clients[slot].ssl = NULL;
}
if (clients[slot].fd != -1) {
close(clients[slot].fd);
clients[slot].fd = -1;
}
clients[slot].tls_established = 0;
}
// TLSハンドシェイクを処理
int handle_tls_handshake(int slot) {
int result = SSL_accept(clients[slot].ssl);
if (result == 1) {
// ハンドシェイク成功
clients[slot].tls_established = 1;
printf("TLS handshake completed for client %d\n", clients[slot].fd);
return 1;
} else {
int error = SSL_get_error(clients[slot].ssl, result);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
// まだハンドシェイク中
return 0;
} else {
// ハンドシェイクエラー
printf("TLS handshake failed for client %d: %d\n", clients[slot].fd, error);
ERR_print_errors_fp(stderr);
return -1;
}
}
}
// HTTPリクエストを処理
void handle_request(int slot) {
char buffer[BUFFER_SIZE];
int bytes_read;
// TLS経由でリクエストを読み込み
bytes_read = SSL_read(clients[slot].ssl, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("HTTPS Request received from client %d:\n%s\n", clients[slot].fd, buffer);
// TLS経由でHTTPレスポンスを送信
SSL_write(clients[slot].ssl, http_response, strlen(http_response));
} else {
int error = SSL_get_error(clients[slot].ssl, bytes_read);
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
printf("SSL_read error for client %d: %d\n", clients[slot].fd, error);
}
}
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
fd_set read_fds, master_fds;
int max_fd;
int opt = 1;
// OpenSSLを初期化
if (!init_openssl()) {
fprintf(stderr, "Failed to initialize OpenSSL\n");
exit(EXIT_FAILURE);
}
// SSL contextを作成
ssl_ctx = create_ssl_context();
if (!ssl_ctx) {
fprintf(stderr, "Failed to create SSL context\n");
exit(EXIT_FAILURE);
}
// SSL contextを設定
if (!configure_ssl_context(ssl_ctx)) {
fprintf(stderr, "Failed to configure SSL context\n");
exit(EXIT_FAILURE);
}
// クライアント情報を初期化
init_clients();
// サーバーソケットを作成
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// SO_REUSEADDRオプションを設定
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// サーバーアドレスを設定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// ソケットをバインド
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
// リスニング開始
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// サーバーソケットを非ブロッキングモードに設定
if (set_nonblocking(server_fd) < 0) {
perror("set_nonblocking");
exit(EXIT_FAILURE);
}
printf("HTTPS Server started on port %d\n", PORT);
printf("Access: https://localhost:%d\n", PORT);
printf("Note: You may see a security warning due to self-signed certificate\n");
// fd_setを初期化
FD_ZERO(&master_fds);
FD_SET(server_fd, &master_fds);
max_fd = server_fd;
while (1) {
// master_fdsをread_fdsにコピー
read_fds = master_fds;
// selectでI/O多重化
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("select");
break;
}
// 各ファイルディスクリプタをチェック
for (int fd = 0; fd <= max_fd; fd++) {
if (FD_ISSET(fd, &read_fds)) {
if (fd == server_fd) {
// 新しい接続を受け入れ
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd >= 0) {
int slot = find_free_client_slot();
if (slot == -1) {
printf("Maximum clients reached, rejecting connection\n");
close(client_fd);
continue;
}
printf("New connection from %s:%d (fd: %d, slot: %d)\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
client_fd, slot);
// クライアントソケットを非ブロッキングモードに設定
if (set_nonblocking(client_fd) < 0) {
perror("set_nonblocking client");
close(client_fd);
continue;
}
// SSLオブジェクトを作成
SSL *ssl = SSL_new(ssl_ctx);
if (!ssl) {
ERR_print_errors_fp(stderr);
close(client_fd);
continue;
}
SSL_set_fd(ssl, client_fd);
// クライアント情報を設定
clients[slot].fd = client_fd;
clients[slot].ssl = ssl;
clients[slot].tls_established = 0;
// クライアントソケットをfd_setに追加
FD_SET(client_fd, &master_fds);
if (client_fd > max_fd) {
max_fd = client_fd;
}
}
} else {
// 既存のクライアントからのデータを処理
int slot = find_client_by_fd(fd);
if (slot == -1) {
// 不明なファイルディスクリプタ
FD_CLR(fd, &master_fds);
close(fd);
continue;
}
if (!clients[slot].tls_established) {
// TLSハンドシェイクを処理
int result = handle_tls_handshake(slot);
if (result < 0) {
// ハンドシェイクエラー
FD_CLR(fd, &master_fds);
remove_client(slot);
}
// result == 0の場合はまだハンドシェイク中なので継続
} else {
// HTTPリクエストを処理
handle_request(slot);
// 接続を閉じる(HTTP/1.1 Connection: close)
FD_CLR(fd, &master_fds);
remove_client(slot);
printf("Client %d disconnected\n", fd);
}
// max_fdを更新
if (fd == max_fd) {
while (max_fd > server_fd && !FD_ISSET(max_fd, &master_fds)) {
max_fd--;
}
}
}
}
}
}
// クリーンアップ
close(server_fd);
for (int i = 0; i < MAX_CLIENTS; i++) {
remove_client(i);
}
SSL_CTX_free(ssl_ctx);
cleanup_openssl();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment