diff --git a/.gitignore b/.gitignore index 1a947e3084..8621208270 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ package/target package/*.gz package/*.ipk target +_codeql_detected_source_root diff --git a/src/dns_client/client_http2.c b/src/dns_client/client_http2.c new file mode 100644 index 0000000000..b5988cd192 --- /dev/null +++ b/src/dns_client/client_http2.c @@ -0,0 +1,281 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE + +#include "client_http2.h" +#include "client_socket.h" +#include "client_tls.h" +#include "server_info.h" + +#include "smartdns/http_parse.h" + +/* Include http2_parse header from http_parse directory */ +#include "../http_parse/http2_parse.h" + +#include +#include + +int _dns_client_send_http2_preface(struct dns_server_info *server_info) +{ + unsigned char buffer[256]; + int offset = 0; + int send_len = 0; + struct http2_context *ctx = NULL; + + /* Initialize HTTP/2 context if needed */ + if (server_info->http2_ctx == NULL) { + server_info->http2_ctx = http2_context_init(0); /* 0 = client */ + if (server_info->http2_ctx == NULL) { + tlog(TLOG_ERROR, "failed to initialize HTTP/2 context."); + return -1; + } + } + + ctx = (struct http2_context *)server_info->http2_ctx; + + /* Set ALPN for HTTP/2 if not already set */ + if (server_info->flags.https.alpn[0] == '\0') { + /* Set ALPN to "h2" for HTTP/2 */ + strncpy(server_info->flags.https.alpn, "h2", DNS_MAX_ALPN_LEN - 1); + server_info->flags.https.alpn[DNS_MAX_ALPN_LEN - 1] = '\0'; + tlog(TLOG_DEBUG, "Setting ALPN to h2 for HTTP/2"); + } + + /* Generate preface using http2_parse function */ + offset = http2_handshake(ctx, buffer, sizeof(buffer)); + if (offset < 0) { + tlog(TLOG_ERROR, "failed to generate HTTP/2 preface."); + return -1; + } + + /* Send the preface and SETTINGS */ + if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { + return _dns_client_send_data_to_buffer(server_info, buffer, offset); + } + + if (server_info->ssl == NULL) { + errno = EINVAL; + return -1; + } + + send_len = _dns_client_socket_ssl_send(server_info, buffer, offset); + if (send_len <= 0) { + if (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) { + return _dns_client_send_data_to_buffer(server_info, buffer, offset); + } else if (server_info->ssl && errno != ENOMEM) { + _dns_client_shutdown_socket(server_info); + } + return -1; + } else if (send_len < offset) { + return _dns_client_send_data_to_buffer(server_info, buffer + send_len, offset - send_len); + } + + return 0; +} + +int _dns_client_send_http2(struct dns_server_info *server_info, void *packet, unsigned short len) +{ + int send_len = 0; + int http_len = 0; + unsigned char inpacket_data[DNS_IN_PACKSIZE]; + unsigned char *inpacket = inpacket_data; + struct client_dns_server_flag_https *https_flag = NULL; + struct http_head *http_head = NULL; + struct http2_context *ctx = NULL; + struct http2_stream *stream = NULL; + + if (len > sizeof(inpacket_data) - 512) { + tlog(TLOG_ERROR, "packet size is invalid."); + return -1; + } + + /* Initialize HTTP/2 context if needed */ + if (server_info->http2_ctx == NULL) { + server_info->http2_ctx = http2_context_init(0); /* 0 = client */ + if (server_info->http2_ctx == NULL) { + tlog(TLOG_ERROR, "failed to initialize HTTP/2 context."); + return -1; + } + } + + ctx = (struct http2_context *)server_info->http2_ctx; + + /* Get an available stream using http2_poll */ + if (http2_poll(ctx, &stream) != 0 || stream == NULL) { + tlog(TLOG_ERROR, "failed to get available HTTP/2 stream."); + return -1; + } + + stream->state = HTTP2_STREAM_OPEN; + + https_flag = &server_info->flags.https; + + /* Build HTTP/2 request using http_head */ + http_head = http_head_init(4096, HTTP_VERSION_2_0); + if (http_head == NULL) { + tlog(TLOG_ERROR, "init http head failed."); + http2_stream_close(ctx, stream); + return -1; + } + + http_head_set_method(http_head, HTTP_METHOD_POST); + http_head_set_url(http_head, https_flag->path); + http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); + http_head_add_fields(http_head, ":authority", https_flag->httphost); + http_head_add_fields(http_head, "content-type", "application/dns-message"); + http_head_add_fields(http_head, "accept", "application/dns-message"); + http_head_add_fields(http_head, "user-agent", "smartdns"); + http_head_set_data(http_head, packet, len); + + http_len = http_head_serialize(http_head, inpacket, DNS_IN_PACKSIZE); + http_head_destroy(http_head); + + if (http_len <= 0) { + tlog(TLOG_ERROR, "serialize http head failed."); + http2_stream_close(ctx, stream); + return -1; + } + + /* Store request data in stream for tracking */ + stream->request_data = packet; + stream->request_data_len = len; + + if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { + return _dns_client_send_data_to_buffer(server_info, inpacket, http_len); + } + + if (server_info->ssl == NULL) { + errno = EINVAL; + http2_stream_close(ctx, stream); + return -1; + } + + send_len = _dns_client_socket_ssl_send(server_info, inpacket, http_len); + if (send_len <= 0) { + if (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) { + /* save data to buffer, and retry when EPOLLOUT is available */ + return _dns_client_send_data_to_buffer(server_info, inpacket, http_len); + } else if (server_info->ssl && errno != ENOMEM) { + _dns_client_shutdown_socket(server_info); + } + http2_stream_close(ctx, stream); + return -1; + } else if (send_len < http_len) { + /* save remain data to buffer, and retry when EPOLLOUT is available */ + return _dns_client_send_data_to_buffer(server_info, inpacket + send_len, http_len - send_len); + } + + /* Stream will be closed after receiving response */ + + return 0; +} + +int _dns_client_process_http2_response(struct dns_server_info *server_info) +{ + int len = 0; + struct http_head *http_head = NULL; + unsigned char *inpacket_data = NULL; + int dns_packet_len = 0; + int ret = -1; + struct http2_context *ctx = NULL; + struct http2_stream *stream = NULL; + + /* Get HTTP/2 context */ + ctx = (struct http2_context *)server_info->http2_ctx; + if (ctx == NULL || !ctx->initialized) { + tlog(TLOG_ERROR, "HTTP/2 context not initialized."); + return -1; + } + + http_head = http_head_init(8192, HTTP_VERSION_2_0); + if (http_head == NULL) { + goto out; + } + + len = http_head_parse(http_head, server_info->recv_buff.data, server_info->recv_buff.len); + if (len < 0) { + if (len == -1) { + /* Incomplete response, wait for more data */ + ret = 0; + goto out; + } else if (len == -3) { + /* Response is too large */ + tlog(TLOG_DEBUG, "http2 response is too large."); + server_info->recv_buff.len = 0; + goto out; + } + + tlog(TLOG_DEBUG, "remote server not supported or parse error."); + goto out; + } + + if (http_head_get_httpcode(http_head) == 0) { + /* Invalid HTTP/2 response */ + server_info->prohibit = 1; + goto out; + } + + if (http_head_get_httpcode(http_head) != 200) { + tlog(TLOG_WARN, "http2 server query from %s:%d failed, server return http code : %d, %s", server_info->ip, + server_info->port, http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head)); + server_info->prohibit = 1; + goto out; + } + + inpacket_data = (unsigned char *)http_head_get_data(http_head); + dns_packet_len = http_head_get_data_len(http_head); + + if (inpacket_data == NULL || dns_packet_len <= 0) { + tlog(TLOG_WARN, "recv http2 packet from %s, len = %d", server_info->ip, len); + goto out; + } + + tlog(TLOG_DEBUG, "recv http2 packet from %s, len = %d", server_info->ip, len); + time(&server_info->last_recv); + + /* Process DNS result */ + if (_dns_client_recv(server_info, inpacket_data, dns_packet_len, &server_info->addr, server_info->ai_addrlen) != + 0) { + goto out; + } + + /* Close the stream for this request (we use stream ID 1 for simplicity in DoH) */ + /* In a more complete implementation, we would track which stream ID corresponds to which request */ + if (!list_empty(&ctx->stream_list)) { + stream = list_first_entry(&ctx->stream_list, struct http2_stream, list); + http2_stream_close(ctx, stream); + } + + /* Remove processed data from buffer */ + server_info->recv_buff.len -= len; + if (server_info->recv_buff.len < 0) { + server_info->recv_buff.len = 0; + } + + if (server_info->recv_buff.len > 0) { + memmove(server_info->recv_buff.data, server_info->recv_buff.data + len, server_info->recv_buff.len); + } + + ret = 0; +out: + if (http_head) { + http_head_destroy(http_head); + } + return ret; +} diff --git a/src/dns_client/client_http2.h b/src/dns_client/client_http2.h new file mode 100644 index 0000000000..1ce8447eba --- /dev/null +++ b/src/dns_client/client_http2.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _DNS_CLIENT_HTTP2_H_ +#define _DNS_CLIENT_HTTP2_H_ + +#include "dns_client.h" +#include "smartdns/http_parse.h" + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus */ + +/* Send HTTP/2 connection preface and SETTINGS frame */ +int _dns_client_send_http2_preface(struct dns_server_info *server_info); + +/* Send HTTP/2 DoH request */ +int _dns_client_send_http2(struct dns_server_info *server_info, void *packet, unsigned short len); + +/* Process HTTP/2 response */ +int _dns_client_process_http2_response(struct dns_server_info *server_info); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_client/client_https.c b/src/dns_client/client_https.c index 97d3f3a7d9..5b9378f205 100644 --- a/src/dns_client/client_https.c +++ b/src/dns_client/client_https.c @@ -18,12 +18,31 @@ #define _GNU_SOURCE #include "client_https.h" +#include "client_http2.h" #include "client_socket.h" #include "client_tls.h" #include "server_info.h" #include "smartdns/http_parse.h" +/* Check if ALPN negotiated HTTP/2 */ +static int _dns_client_is_http2(struct dns_server_info *server_info) +{ + const unsigned char *alpn_data = NULL; + unsigned int alpn_len = 0; + + if (server_info->ssl == NULL) { + return 0; + } + + SSL_get0_alpn_selected(server_info->ssl, &alpn_data, &alpn_len); + if (alpn_data && alpn_len == 2 && memcmp(alpn_data, "h2", 2) == 0) { + return 1; + } + + return 0; +} + int _dns_client_send_https(struct dns_server_info *server_info, void *packet, unsigned short len) { int send_len = 0; @@ -37,6 +56,11 @@ int _dns_client_send_https(struct dns_server_info *server_info, void *packet, un return -1; } + /* Check if connection uses HTTP/2 */ + if (_dns_client_is_http2(server_info)) { + return _dns_client_send_http2(server_info, packet, len); + } + https_flag = &server_info->flags.https; http_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE, @@ -81,3 +105,14 @@ int _dns_client_send_https(struct dns_server_info *server_info, void *packet, un return 0; } + +int _dns_client_send_https_preface(struct dns_server_info *server_info) +{ + /* Check if connection uses HTTP/2 */ + if (_dns_client_is_http2(server_info)) { + return _dns_client_send_http2_preface(server_info); + } + + /* HTTP/1.1 doesn't need preface */ + return 0; +} diff --git a/src/dns_client/client_https.h b/src/dns_client/client_https.h index 22c30bed17..d4712b1b49 100644 --- a/src/dns_client/client_https.h +++ b/src/dns_client/client_https.h @@ -27,6 +27,8 @@ extern "C" { int _dns_client_send_https(struct dns_server_info *server_info, void *packet, unsigned short len); +int _dns_client_send_https_preface(struct dns_server_info *server_info); + #ifdef __cplusplus } #endif /*__cplusplus */ diff --git a/src/dns_client/client_socket.c b/src/dns_client/client_socket.c index ea33e571d3..21388bf534 100644 --- a/src/dns_client/client_socket.c +++ b/src/dns_client/client_socket.c @@ -62,8 +62,12 @@ int _dns_client_create_socket(struct dns_server_info *server_info) ret = _dns_client_create_socket_quic(server_info, flag_tls->hostname, alpn); } else if (server_info->type == DNS_SERVER_HTTPS) { struct client_dns_server_flag_https *flag_https = NULL; + const char *alpn = "h2"; /* Default: HTTP/2 for DoH */ flag_https = &server_info->flags.https; - ret = _dns_client_create_socket_tls(server_info, flag_https->hostname, flag_https->alpn); + if (flag_https->alpn[0] != 0) { + alpn = flag_https->alpn; /* User can override with "-alpn http/1.1" or other values */ + } + ret = _dns_client_create_socket_tls(server_info, flag_https->hostname, alpn); } else if (server_info->type == DNS_SERVER_HTTP3) { struct client_dns_server_flag_https *flag_https = NULL; const char *alpn = "h3"; diff --git a/src/dns_client/client_tcp.c b/src/dns_client/client_tcp.c index c40d927c2f..df1dffcd69 100644 --- a/src/dns_client/client_tcp.c +++ b/src/dns_client/client_tcp.c @@ -155,7 +155,21 @@ static int _dns_client_process_tcp_buff(struct dns_server_info *server_info) while (1) { if (server_info->type == DNS_SERVER_HTTPS) { - http_head = http_head_init(4096, HTTP_VERSION_1_1); + HTTP_VERSION http_version = HTTP_VERSION_1_1; + + /* Auto-detect HTTP version by checking for HTTP/2 frame header */ + /* HTTP/2 frame starts with 3-byte length, 1-byte type, 1-byte flags, 4-byte stream ID */ + /* HTTP/1.1 response starts with "HTTP/1.1" */ + /* Note: This heuristic works because HTTP/2 frames never start with 'H' (ASCII 0x48) */ + /* as the first byte is part of the 24-bit length field (max 16MB = 0xFFFFFF) */ + if (server_info->recv_buff.len >= 9) { + /* Check if this looks like an HTTP/2 frame (not starting with "HTTP") */ + if (server_info->recv_buff.data[0] != 'H') { + http_version = HTTP_VERSION_2_0; + } + } + + http_head = http_head_init(8192, http_version); if (http_head == NULL) { goto out; } diff --git a/src/dns_client/client_tls.c b/src/dns_client/client_tls.c index c3dcdf36f7..6bc2823eb1 100644 --- a/src/dns_client/client_tls.c +++ b/src/dns_client/client_tls.c @@ -19,6 +19,7 @@ #define _GNU_SOURCE #include "client_tls.h" +#include "client_https.h" #include "client_quic.h" #include "client_socket.h" #include "client_tcp.h" @@ -1050,6 +1051,12 @@ int _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_ev } server_info->status = DNS_SERVER_STATUS_CONNECTED; + + /* Send HTTP/2 preface if this is an HTTPS connection with HTTP/2 */ + if (server_info->type == DNS_SERVER_HTTPS) { + _dns_client_send_https_preface(server_info); + } + memset(&fd_event, 0, sizeof(fd_event)); fd_event.events = EPOLLIN | EPOLLOUT; fd_event.data.ptr = server_info; diff --git a/src/dns_client/dns_client.h b/src/dns_client/dns_client.h index 2f21707234..5c8a2e5aba 100644 --- a/src/dns_client/dns_client.h +++ b/src/dns_client/dns_client.h @@ -132,6 +132,9 @@ struct dns_server_info { struct list_head conn_stream_list; dns_server_security_status security_status; + + /* HTTP/2 connection context (for HTTPS with HTTP/2) */ + void *http2_ctx; }; struct dns_server_pending_group { diff --git a/src/dns_server/connection.c b/src/dns_server/connection.c index db73cf5ac1..d62e124faf 100644 --- a/src/dns_server/connection.c +++ b/src/dns_server/connection.c @@ -18,6 +18,7 @@ #include "connection.h" #include "dns_server.h" +#include "server_http2.h" #include #include @@ -59,6 +60,10 @@ void _dns_server_conn_release(struct dns_server_conn_head *conn) SSL_free(tls_client->ssl); tls_client->ssl = NULL; } + /* Clean up HTTP/2 context if it exists */ + if (tls_client->http2_ctx != NULL) { + _dns_server_http2_destroy_context(tls_client); + } pthread_mutex_destroy(&tls_client->ssl_lock); } else if (conn->type == DNS_CONN_TYPE_TLS_SERVER || conn->type == DNS_CONN_TYPE_HTTPS_SERVER) { struct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn; diff --git a/src/dns_server/dns_server.h b/src/dns_server/dns_server.h index c8bb64a139..a96b5d7226 100644 --- a/src/dns_server/dns_server.h +++ b/src/dns_server/dns_server.h @@ -36,6 +36,9 @@ extern "C" { #endif /*__cplusplus */ +/* Forward declaration */ +struct http2_context; + #define DNS_MAX_EVENTS 256 #define IPV6_READY_CHECK_TIME 180 #define DNS_SERVER_TMOUT_TTL (5 * 60) @@ -211,6 +214,9 @@ struct dns_server_conn_tls_client { SSL *ssl; int ssl_want_write; pthread_mutex_t ssl_lock; + + /* HTTP/2 context for DoH */ + struct http2_context *http2_ctx; }; /* ip address lists of domain */ diff --git a/src/dns_server/server_http2.c b/src/dns_server/server_http2.c new file mode 100644 index 0000000000..033904d12e --- /dev/null +++ b/src/dns_server/server_http2.c @@ -0,0 +1,197 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "server_http2.h" +#include "dns_server.h" +#include "server_https.h" +#include "server_tcp.h" + +#include "../http_parse/http2_parse.h" + +#include +#include + +int _dns_server_http2_init_context(struct dns_server_conn_tls_client *tls_client) +{ + if (tls_client->http2_ctx != NULL) { + return 0; + } + + /* Initialize HTTP/2 context for server (uses even stream IDs) */ + tls_client->http2_ctx = http2_context_init(1); + if (tls_client->http2_ctx == NULL) { + tlog(TLOG_ERROR, "Failed to initialize HTTP/2 context"); + return -1; + } + + return 0; +} + +void _dns_server_http2_destroy_context(struct dns_server_conn_tls_client *tls_client) +{ + if (tls_client->http2_ctx != NULL) { + http2_context_destroy(tls_client->http2_ctx); + tls_client->http2_ctx = NULL; + } +} + +int _dns_server_process_http2_request(struct dns_server_conn_tls_client *tls_client, unsigned char *data, int data_len, + unsigned char **request_data, int *request_len) +{ + struct http_head *http_head = NULL; + int len = 0; + + /* Initialize HTTP/2 context if needed */ + if (tls_client->http2_ctx == NULL) { + if (_dns_server_http2_init_context(tls_client) != 0) { + return -1; + } + } + + /* Parse HTTP/2 frames */ + http_head = http_head_init(4096, HTTP_VERSION_2_0); + if (http_head == NULL) { + return -1; + } + + len = http_head_parse(http_head, data, data_len); + if (len < 0) { + http_head_destroy(http_head); + if (len == -1) { + /* Need more data */ + return 0; + } + tlog(TLOG_DEBUG, "Failed to parse HTTP/2 frame"); + return -1; + } + + /* Check if this is a POST request with DNS data */ + if (http_head_get_method(http_head) == HTTP_METHOD_POST) { + const char *content_type = http_head_get_fields_value(http_head, "Content-Type"); + if (content_type == NULL || + strncasecmp(content_type, "application/dns-message", sizeof("application/dns-message")) != 0) { + tlog(TLOG_DEBUG, "Invalid content type for DoH: %s", content_type); + http_head_destroy(http_head); + return -1; + } + + *request_len = http_head_get_data_len(http_head); + if (*request_len <= 0 || *request_len >= DNS_IN_PACKSIZE) { + tlog(TLOG_DEBUG, "Invalid DNS request length: %d", *request_len); + http_head_destroy(http_head); + return -1; + } + + *request_data = (unsigned char *)http_head_get_data(http_head); + } else if (http_head_get_method(http_head) == HTTP_METHOD_GET) { + /* GET requests not typically used in server-to-server DoH */ + tlog(TLOG_DEBUG, "GET method not supported in server HTTP/2"); + http_head_destroy(http_head); + return -1; + } else { + tlog(TLOG_DEBUG, "Unsupported HTTP method"); + http_head_destroy(http_head); + return -1; + } + + /* Don't destroy http_head yet - request_data points to it */ + /* Caller must handle this */ + + return len; +} + +int _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_tls_client *tls_client, void *packet, + unsigned short len) +{ + struct http_head *http_head = NULL; + unsigned char response_buffer[DNS_IN_PACKSIZE]; + int response_len = 0; + int ret = 0; + + /* Initialize HTTP/2 context if needed */ + if (tls_client->http2_ctx == NULL) { + if (_dns_server_http2_init_context(tls_client) != 0) { + return -1; + } + } + + /* Build HTTP/2 response */ + http_head = http_head_init(4096, HTTP_VERSION_2_0); + if (http_head == NULL) { + return -1; + } + + http_head->head_type = HTTP_HEAD_RESPONSE; + http_head->code = 200; + http_head->data = packet; + http_head->data_len = len; + + /* Set response headers */ + http_head_add_fields(http_head, "Content-Type", "application/dns-message"); + + /* Serialize HTTP/2 response */ + response_len = http_head_serialize_http2_0(http_head, response_buffer, sizeof(response_buffer)); + if (response_len < 0) { + tlog(TLOG_ERROR, "Failed to serialize HTTP/2 response"); + http_head_destroy(http_head); + return -1; + } + + http_head_destroy(http_head); + + /* Send response */ + ret = _dns_server_tcp_socket_send(&tls_client->tcp, response_buffer, response_len); + if (ret < 0) { + if (errno == EAGAIN) { + return _dns_server_reply_tcp_to_buffer(&tls_client->tcp, response_buffer, response_len); + } + return -1; + } else if (ret < response_len) { + return _dns_server_reply_tcp_to_buffer(&tls_client->tcp, response_buffer + ret, response_len - ret); + } + + return 0; +} + +int _dns_server_is_http2_request(unsigned char *data, int data_len) +{ + /* HTTP/2 frame starts with 24-bit length + 8-bit type + 8-bit flags + 32-bit stream ID + * HTTP/1.x starts with method name ("GET", "POST", etc) or "HTTP/" + * + * Check if this looks like an HTTP/2 frame: + * - First byte is NOT 'H', 'G', 'P', 'D', etc. (HTTP/1.x methods or response) + * - Minimum frame is 9 bytes (frame header) + */ + if (data_len < 9) { + return 0; + } + + /* HTTP/1.x always starts with ASCII characters */ + if (data[0] == 'H' || data[0] == 'G' || data[0] == 'P' || data[0] == 'D' || + data[0] == 'O' || data[0] == 'T' || data[0] == 'C') { + return 0; + } + + /* HTTP/2 frames typically have small lengths in first 3 bytes */ + /* And frame type in byte 3 should be <= 9 (CONTINUATION) */ + if (data[3] <= 9) { + return 1; + } + + return 0; +} diff --git a/src/dns_server/server_http2.h b/src/dns_server/server_http2.h new file mode 100644 index 0000000000..73de595810 --- /dev/null +++ b/src/dns_server/server_http2.h @@ -0,0 +1,48 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _DNS_SERVER_HTTP2_H_ +#define _DNS_SERVER_HTTP2_H_ + +#include "dns_server.h" + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus */ + +/* Initialize HTTP/2 context for server connection */ +int _dns_server_http2_init_context(struct dns_server_conn_tls_client *tls_client); + +/* Destroy HTTP/2 context */ +void _dns_server_http2_destroy_context(struct dns_server_conn_tls_client *tls_client); + +/* Process HTTP/2 request and extract DNS query data */ +int _dns_server_process_http2_request(struct dns_server_conn_tls_client *tls_client, unsigned char *data, int data_len, + unsigned char **request_data, int *request_len); + +/* Send HTTP/2 DNS response */ +int _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_tls_client *tls_client, void *packet, + unsigned short len); + +/* Check if data is HTTP/2 frame */ +int _dns_server_is_http2_request(unsigned char *data, int data_len); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_server/server_https.c b/src/dns_server/server_https.c index fb923146f9..3d99507771 100644 --- a/src/dns_server/server_https.c +++ b/src/dns_server/server_https.c @@ -17,6 +17,7 @@ */ #include "server_https.h" +#include "server_http2.h" #include "connection.h" #include "dns_server.h" #include "server_socket.h" @@ -68,6 +69,16 @@ int _dns_server_reply_https(struct dns_request *request, struct dns_server_conn_ return -1; } + /* Check if this is an HTTP/2 connection */ + if (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { + struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcpclient; + if (tls_client->http2_ctx != NULL) { + /* Use HTTP/2 response */ + return _dns_server_reply_http2(request, tls_client, packet, len); + } + } + + /* HTTP/1.1 response */ http_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE, "HTTP/1.1 200 OK\r\n" "Content-Type: application/dns-message\r\n" diff --git a/src/dns_server/server_tcp.c b/src/dns_server/server_tcp.c index 5b7c692395..a47df0c255 100644 --- a/src/dns_server/server_tcp.c +++ b/src/dns_server/server_tcp.c @@ -22,10 +22,12 @@ #include "connection.h" #include "dns_server.h" #include "server_https.h" +#include "server_http2.h" #include "server_socket.h" #include "server_tls.h" #include "smartdns/http_parse.h" +#include "../http_parse/http2_parse.h" #include #include @@ -254,89 +256,129 @@ static int _dns_server_tcp_process_one_request(struct dns_server_conn_tcp_client goto out; } - http_head = http_head_init(4096, HTTP_VERSION_1_1); - if (http_head == NULL) { - goto out; + /* Check if this is HTTP/2 or HTTP/1.1 */ + int is_http2 = 0; + if (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { + /* Cast to TLS client to access http2_ctx */ + struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcpclient; + + /* If http2_ctx exists, we're using HTTP/2 */ + if (tls_client->http2_ctx != NULL) { + is_http2 = 1; + } else if (_dns_server_is_http2_request(tcpclient->recvbuff.buf + proceed_len, + tcpclient->recvbuff.size - proceed_len)) { + is_http2 = 1; + } } - len = http_head_parse(http_head, tcpclient->recvbuff.buf + proceed_len, tcpclient->recvbuff.size - proceed_len); - if (len < 0) { - if (len == -1) { - ret = 0; - goto out; - } else if (len == -3) { - tcpclient->recvbuff.size = 0; - tlog(TLOG_DEBUG, "recv buffer is not enough."); + if (is_http2) { + /* Handle HTTP/2 request */ + struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcpclient; + unsigned char *http2_request_data = NULL; + int http2_request_len = 0; + + len = _dns_server_process_http2_request(tls_client, + tcpclient->recvbuff.buf + proceed_len, + tcpclient->recvbuff.size - proceed_len, + &http2_request_data, &http2_request_len); + if (len < 0) { + tlog(TLOG_DEBUG, "Failed to process HTTP/2 request"); goto errout; + } else if (len == 0) { + /* Need more data */ + ret = RECV_ERROR_AGAIN; + goto out; + } + + request_data = http2_request_data; + request_len = http2_request_len; + proceed_len += len; + } else { + /* Handle HTTP/1.1 request */ + http_head = http_head_init(4096, HTTP_VERSION_1_1); + if (http_head == NULL) { + goto out; } - tlog(TLOG_DEBUG, "parser http header failed."); - goto errout; - } + len = http_head_parse(http_head, tcpclient->recvbuff.buf + proceed_len, tcpclient->recvbuff.size - proceed_len); + if (len < 0) { + if (len == -1) { + ret = 0; + goto out; + } else if (len == -3) { + tcpclient->recvbuff.size = 0; + tlog(TLOG_DEBUG, "recv buffer is not enough."); + goto errout; + } - if (http_head_get_method(http_head) == HTTP_METHOD_POST) { - const char *content_type = http_head_get_fields_value(http_head, "Content-Type"); - if (content_type == NULL || - strncasecmp(content_type, "application/dns-message", sizeof("application/dns-message")) != 0) { - tlog(TLOG_DEBUG, "content type not supported, %s", content_type); + tlog(TLOG_DEBUG, "parser http header failed."); goto errout; } - request_len = http_head_get_data_len(http_head); - if (request_len >= len) { - tlog(TLOG_DEBUG, "request length is invalid."); - goto errout; - } - request_data = (unsigned char *)http_head_get_data(http_head); - } else if (http_head_get_method(http_head) == HTTP_METHOD_GET) { - const char *path = http_head_get_url(http_head); - if (path == NULL || strncasecmp(path, "/dns-query", sizeof("/dns-query")) != 0) { - ret = RECV_ERROR_BAD_PATH; - tlog(TLOG_DEBUG, "path not supported, %s", path); - goto errout; - } + if (http_head_get_method(http_head) == HTTP_METHOD_POST) { + const char *content_type = http_head_get_fields_value(http_head, "Content-Type"); + if (content_type == NULL || + strncasecmp(content_type, "application/dns-message", sizeof("application/dns-message")) != 0) { + tlog(TLOG_DEBUG, "content type not supported, %s", content_type); + goto errout; + } - const char *dns_query = http_head_get_params_value(http_head, "dns"); - if (dns_query == NULL) { - tlog(TLOG_DEBUG, "query is null."); - goto errout; - } + request_len = http_head_get_data_len(http_head); + if (request_len >= len) { + tlog(TLOG_DEBUG, "request length is invalid."); + goto errout; + } + request_data = (unsigned char *)http_head_get_data(http_head); + } else if (http_head_get_method(http_head) == HTTP_METHOD_GET) { + const char *path = http_head_get_url(http_head); + if (path == NULL || strncasecmp(path, "/dns-query", sizeof("/dns-query")) != 0) { + ret = RECV_ERROR_BAD_PATH; + tlog(TLOG_DEBUG, "path not supported, %s", path); + goto errout; + } - if (base64_query == NULL) { - base64_query = malloc(DNS_IN_PACKSIZE); - if (base64_query == NULL) { - tlog(TLOG_DEBUG, "malloc failed."); + const char *dns_query = http_head_get_params_value(http_head, "dns"); + if (dns_query == NULL) { + tlog(TLOG_DEBUG, "query is null."); goto errout; } - } - if (urldecode(base64_query, DNS_IN_PACKSIZE, dns_query) < 0) { - tlog(TLOG_DEBUG, "urldecode query failed."); - goto errout; - } + if (base64_query == NULL) { + base64_query = malloc(DNS_IN_PACKSIZE); + if (base64_query == NULL) { + tlog(TLOG_DEBUG, "malloc failed."); + goto errout; + } + } + + if (urldecode(base64_query, DNS_IN_PACKSIZE, dns_query) < 0) { + tlog(TLOG_DEBUG, "urldecode query failed."); + goto errout; + } - if (http_decode_data == NULL) { - http_decode_data = malloc(DNS_IN_PACKSIZE); if (http_decode_data == NULL) { - tlog(TLOG_DEBUG, "malloc failed."); + http_decode_data = malloc(DNS_IN_PACKSIZE); + if (http_decode_data == NULL) { + tlog(TLOG_DEBUG, "malloc failed."); + goto errout; + } + } + + int decode_len = SSL_base64_decode_ext(base64_query, http_decode_data, DNS_IN_PACKSIZE, 1, 1); + if (decode_len <= 0) { + tlog(TLOG_DEBUG, "decode query failed."); goto errout; } - } - int decode_len = SSL_base64_decode_ext(base64_query, http_decode_data, DNS_IN_PACKSIZE, 1, 1); - if (decode_len <= 0) { - tlog(TLOG_DEBUG, "decode query failed."); + request_len = decode_len; + request_data = http_decode_data; + } else { + tlog(TLOG_DEBUG, "http method is invalid."); goto errout; } - request_len = decode_len; - request_data = http_decode_data; - } else { - tlog(TLOG_DEBUG, "http method is invalid."); - goto errout; + proceed_len += len; } - - proceed_len += len; } else { if ((total_len - proceed_len) <= (int)sizeof(unsigned short)) { ret = RECV_ERROR_AGAIN; diff --git a/src/dns_server/server_tls.c b/src/dns_server/server_tls.c index f9d2249c6b..7ddbe04928 100644 --- a/src/dns_server/server_tls.c +++ b/src/dns_server/server_tls.c @@ -393,6 +393,30 @@ static int _dns_server_socket_tls_ssl_pass_callback(char *buf, int size, int rwf return strlen(buf); } +/* ALPN callback for HTTP/2 negotiation */ +static int _dns_server_tls_alpn_select_callback(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + /* Supported protocols: h2 (HTTP/2), http/1.1 */ + static const unsigned char alpn_h2[] = "\x02h2"; + static const unsigned char alpn_http11[] = "\x08http/1.1"; + + /* Try to negotiate HTTP/2 first */ + if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_h2, sizeof(alpn_h2) - 1, in, inlen) == + OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_OK; + } + + /* Fall back to HTTP/1.1 */ + if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_http11, sizeof(alpn_http11) - 1, in, inlen) == + OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_OK; + } + + /* No match */ + return SSL_TLSEXT_ERR_NOACK; +} + int _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type) { const char *host_ip = NULL; @@ -457,6 +481,11 @@ int _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type) goto errout; } + /* Set ALPN callback for HTTP/2 negotiation (for bind-https) */ + if (conn_type == DNS_CONN_TYPE_HTTPS_SERVER) { + SSL_CTX_set_alpn_select_cb(ssl_ctx, _dns_server_tls_alpn_select_callback, NULL); + } + conn = malloc(sizeof(struct dns_server_conn_tls_server)); if (conn == NULL) { goto errout; diff --git a/src/http_parse/hpack.c b/src/http_parse/hpack.c new file mode 100644 index 0000000000..3c0eb238a9 --- /dev/null +++ b/src/http_parse/hpack.c @@ -0,0 +1,118 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hpack.h" +#include + +/* HPACK static table (RFC 7541 Appendix A) - indices 1-61 */ +static struct hpack_header_field hpack_static_table[] = { + {":authority", ""}, /* 1 */ + {":method", "GET"}, /* 2 */ + {":method", "POST"}, /* 3 */ + {":path", "/"}, /* 4 */ + {":path", "/index.html"}, /* 5 */ + {":scheme", "http"}, /* 6 */ + {":scheme", "https"}, /* 7 */ + {":status", "200"}, /* 8 */ + {":status", "204"}, /* 9 */ + {":status", "206"}, /* 10 */ + {":status", "304"}, /* 11 */ + {":status", "400"}, /* 12 */ + {":status", "404"}, /* 13 */ + {":status", "500"}, /* 14 */ + {"accept-charset", ""}, /* 15 */ + {"accept-encoding", "gzip, deflate"}, /* 16 */ + {"accept-language", ""}, /* 17 */ + {"accept-ranges", ""}, /* 18 */ + {"accept", ""}, /* 19 */ + {"access-control-allow-origin", ""}, /* 20 */ + {"age", ""}, /* 21 */ + {"allow", ""}, /* 22 */ + {"authorization", ""}, /* 23 */ + {"cache-control", ""}, /* 24 */ + {"content-disposition", ""}, /* 25 */ + {"content-encoding", ""}, /* 26 */ + {"content-language", ""}, /* 27 */ + {"content-length", ""}, /* 28 */ + {"content-location", ""}, /* 29 */ + {"content-range", ""}, /* 30 */ + {"content-type", ""}, /* 31 */ + {"cookie", ""}, /* 32 */ + {"date", ""}, /* 33 */ + {"etag", ""}, /* 34 */ + {"expect", ""}, /* 35 */ + {"expires", ""}, /* 36 */ + {"from", ""}, /* 37 */ + {"host", ""}, /* 38 */ + {"if-match", ""}, /* 39 */ + {"if-modified-since", ""}, /* 40 */ + {"if-none-match", ""}, /* 41 */ + {"if-range", ""}, /* 42 */ + {"if-unmodified-since", ""}, /* 43 */ + {"last-modified", ""}, /* 44 */ + {"link", ""}, /* 45 */ + {"location", ""}, /* 46 */ + {"max-forwards", ""}, /* 47 */ + {"proxy-authenticate", ""}, /* 48 */ + {"proxy-authorization", ""}, /* 49 */ + {"range", ""}, /* 50 */ + {"referer", ""}, /* 51 */ + {"refresh", ""}, /* 52 */ + {"retry-after", ""}, /* 53 */ + {"server", ""}, /* 54 */ + {"set-cookie", ""}, /* 55 */ + {"strict-transport-security", ""}, /* 56 */ + {"transfer-encoding", ""}, /* 57 */ + {"user-agent", ""}, /* 58 */ + {"vary", ""}, /* 59 */ + {"via", ""}, /* 60 */ + {"www-authenticate", ""}, /* 61 */ +}; + +struct hpack_header_field *hpack_get_static_header_field(int index) +{ + if (index < 1 || index > 61) { + return NULL; + } + return &hpack_static_table[index - 1]; +} + +/* Huffman decode - reuse the QPACK huffman decoder as they share the same encoding */ +/* HPACK and QPACK use the same Huffman coding table from RFC 7541 */ +extern int qpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded, + size_t *nb_decoded); + +int hpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded, + size_t *nb_decoded) +{ + /* HPACK and QPACK use the same Huffman coding, so we can reuse the QPACK decoder */ + return qpack_huffman_decode(bytes, bytes_max, decoded, max_decoded, nb_decoded); +} + +int hpack_huffman_encode(const uint8_t *input, size_t input_len, uint8_t *output, size_t max_output, + size_t *output_len) +{ + /* For now, we don't encode with Huffman - just copy the input */ + /* This is acceptable for DoH as the messages are small */ + if (input_len > max_output) { + return -1; + } + memcpy(output, input, input_len); + *output_len = input_len; + return 0; +} diff --git a/src/http_parse/hpack.h b/src/http_parse/hpack.h new file mode 100644 index 0000000000..e62e3209e4 --- /dev/null +++ b/src/http_parse/hpack.h @@ -0,0 +1,49 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _HPACK_H +#define _HPACK_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct hpack_header_field { + const char *name; + const char *value; +}; + +/* Get static header field by index (1-61) */ +struct hpack_header_field *hpack_get_static_header_field(int index); + +/* Huffman decode for HPACK */ +int hpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded, + size_t *nb_decoded); + +/* Huffman encode for HPACK */ +int hpack_huffman_encode(const uint8_t *input, size_t input_len, uint8_t *output, size_t max_output, + size_t *output_len); + +#ifdef __cplusplus +} +#endif + +#endif // !_HPACK_H diff --git a/src/http_parse/http2_parse.c b/src/http_parse/http2_parse.c index 67dd01ea4c..f4dd89b6a4 100644 --- a/src/http_parse/http2_parse.c +++ b/src/http_parse/http2_parse.c @@ -17,14 +17,943 @@ */ #include "http2_parse.h" +#include "hpack.h" #include "http_parse.h" +#include +#include +#include +#include + +/* HTTP/2 Frame Types (RFC 7540) */ +#define HTTP2_FRAME_DATA 0x0 +#define HTTP2_FRAME_HEADERS 0x1 +#define HTTP2_FRAME_PRIORITY 0x2 +#define HTTP2_FRAME_RST_STREAM 0x3 +#define HTTP2_FRAME_SETTINGS 0x4 +#define HTTP2_FRAME_PUSH_PROMISE 0x5 +#define HTTP2_FRAME_PING 0x6 +#define HTTP2_FRAME_GOAWAY 0x7 +#define HTTP2_FRAME_WINDOW_UPDATE 0x8 +#define HTTP2_FRAME_CONTINUATION 0x9 + +/* HTTP/2 Frame Flags */ +#define HTTP2_FLAG_END_STREAM 0x1 +#define HTTP2_FLAG_END_HEADERS 0x4 +#define HTTP2_FLAG_PADDED 0x8 +#define HTTP2_FLAG_PRIORITY 0x20 + +/* HTTP/2 Frame Header Size */ +#define HTTP2_FRAME_HEADER_SIZE 9 + +/* Encode integer with prefix bits (RFC 7541 Section 5.1) */ +static int _hpack_encode_integer(uint32_t value, int prefix_bits, uint8_t *buffer, int buffer_size) +{ + int max_prefix = (1 << prefix_bits) - 1; + int offset = 0; + + if (value < (uint32_t)max_prefix) { + if (buffer_size < 1) { + return -1; + } + buffer[0] |= value; + return 1; + } + + if (buffer_size < 1) { + return -1; + } + buffer[0] |= max_prefix; + offset = 1; + value -= max_prefix; + + while (value >= 128) { + if (buffer_size - offset < 1) { + return -1; + } + buffer[offset++] = (value & 0x7F) | 0x80; + value >>= 7; + } + + if (buffer_size - offset < 1) { + return -1; + } + buffer[offset++] = value; + + return offset; +} + +/* Decode integer with prefix bits (RFC 7541 Section 5.1) */ +static int _hpack_decode_integer(const uint8_t *buffer, int buffer_len, int prefix_bits, uint32_t *value) +{ + int max_prefix = (1 << prefix_bits) - 1; + int offset = 0; + int m = 0; + + if (buffer_len < 1) { + return -1; + } + + *value = buffer[0] & max_prefix; + if (*value < (uint32_t)max_prefix) { + return 1; + } + + offset = 1; + while (offset < buffer_len) { + uint32_t b = buffer[offset]; + /* Prevent integer overflow: max shift is 21 bits (32 - 7 - 4 reserved bits) */ + if (m > 21) { + return -1; + } + *value += (b & 0x7F) << m; + offset++; + if ((b & 0x80) == 0) { + return offset; + } + m += 7; + } + + return -1; +} + +/* Decode string (RFC 7541 Section 5.2) */ +static int _hpack_decode_string(const uint8_t *buffer, int buffer_len, char *str, int max_str_len, size_t *str_len) +{ + int huffman = (buffer[0] & 0x80) != 0; + uint32_t len = 0; + int offset = 0; + int offset_ret = 0; + + offset_ret = _hpack_decode_integer(buffer, buffer_len, 7, &len); + if (offset_ret < 0) { + return -1; + } + offset = offset_ret; + + if ((uint32_t)(buffer_len - offset) < len) { + return -1; + } + + if (huffman) { + size_t decoded_len = 0; + /* Huffman decode - buffer size checked by decoder */ + if (hpack_huffman_decode(buffer + offset, buffer + offset + len, (uint8_t *)str, max_str_len, &decoded_len) < + 0) { + return -1; + } + if (decoded_len >= (size_t)max_str_len) { + /* Decoded string too large for buffer */ + return -3; + } + str[decoded_len] = '\0'; + *str_len = decoded_len; + } else { + /* Non-Huffman: check buffer size before copy */ + if ((uint32_t)max_str_len < len) { + return -3; + } + memcpy(str, buffer + offset, len); + str[len] = '\0'; + *str_len = len; + } + + return offset + len; +} + +/* Encode string (RFC 7541 Section 5.2) */ +static int _hpack_encode_string(const char *str, int str_len, uint8_t *buffer, int buffer_size) +{ + int offset = 0; + int offset_ret = 0; + + /* We don't use Huffman encoding for simplicity */ + if (buffer_size < 1) { + return -1; + } + + buffer[0] = 0; /* No Huffman encoding */ + offset_ret = _hpack_encode_integer(str_len, 7, buffer, buffer_size); + if (offset_ret < 0) { + return -1; + } + offset = offset_ret; + + if (buffer_size - offset < str_len) { + return -1; + } + + memcpy(buffer + offset, str, str_len); + offset += str_len; + + return offset; +} + +/* Build HPACK header block */ +static int _http2_build_header_block(struct http_head *http_head, uint8_t *buffer, int buffer_len) +{ + int offset = 0; + int offset_ret = 0; + struct http_head_fields *fields = NULL; + struct http_params *params = NULL; + + if (http_head->head_type == HTTP_HEAD_REQUEST) { + char request_path[1024]; + int request_path_len = 0; + + /* Build path with query parameters */ + request_path_len = snprintf(request_path, sizeof(request_path), "%s", http_head->url); + if (request_path_len < 0 || request_path_len >= (int)sizeof(request_path)) { + return -1; + } + + int count = 0; + list_for_each_entry(params, &http_head->params.list, list) + { + int remain = sizeof(request_path) - request_path_len; + int n; + if (count == 0) { + n = snprintf(request_path + request_path_len, remain, "?%s=%s", params->name, params->value); + } else { + n = snprintf(request_path + request_path_len, remain, "&%s=%s", params->name, params->value); + } + if (n < 0 || n >= remain) { + return -3; + } + request_path_len += n; + count++; + } + + /* :method */ + const char *method = http_method_str(http_head->method); + if (http_head->method == HTTP_METHOD_GET) { + /* Indexed header field: :method GET (index 2) */ + if (buffer_len - offset < 1) { + return -1; + } + buffer[offset++] = 0x82; /* 10000010 */ + } else if (http_head->method == HTTP_METHOD_POST) { + /* Indexed header field: :method POST (index 3) */ + if (buffer_len - offset < 1) { + return -1; + } + buffer[offset++] = 0x83; /* 10000011 */ + } else { + /* Literal header field with incremental indexing: :method */ + if (buffer_len - offset < 2) { + return -1; + } + buffer[offset++] = 0x42; /* 01000010 - indexed name :method (index 2) */ + offset_ret = _hpack_encode_string(method, strlen(method), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + } + + /* :path */ + if (strcmp(request_path, "/") == 0) { + /* Indexed header field: :path / (index 4) */ + if (buffer_len - offset < 1) { + return -1; + } + buffer[offset++] = 0x84; /* 10000100 */ + } else { + /* Literal header field with incremental indexing: :path */ + if (buffer_len - offset < 2) { + return -1; + } + buffer[offset++] = 0x44; /* 01000100 - indexed name :path (index 4) */ + offset_ret = _hpack_encode_string(request_path, strlen(request_path), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + } + + /* :scheme https (index 7) */ + if (buffer_len - offset < 1) { + return -1; + } + buffer[offset++] = 0x87; /* 10000111 */ + + } else if (http_head->head_type == HTTP_HEAD_RESPONSE) { + /* :status */ + char status_str[12]; + int n = snprintf(status_str, sizeof(status_str), "%d", http_head->code); + if (n < 0 || n >= (int)sizeof(status_str)) { + return -1; + } + + if (http_head->code == 200) { + /* Indexed header field: :status 200 (index 8) */ + if (buffer_len - offset < 1) { + return -1; + } + buffer[offset++] = 0x88; /* 10001000 */ + } else { + /* Literal header field: :status */ + if (buffer_len - offset < 2) { + return -1; + } + buffer[offset++] = 0x48; /* 01001000 - indexed name :status (index 8) */ + offset_ret = _hpack_encode_string(status_str, strlen(status_str), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + } + } + + /* Add other header fields */ + list_for_each_entry(fields, &http_head->field_head.list, list) + { + /* Literal header field without indexing - new name */ + if (buffer_len - offset < 2) { + return -1; + } + buffer[offset++] = 0x00; /* 00000000 - literal without indexing */ + + offset_ret = _hpack_encode_string(fields->name, strlen(fields->name), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + offset_ret = _hpack_encode_string(fields->value, strlen(fields->value), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + } + + /* Add content-length if data is present */ + if (http_head->data_len > 0 && http_head->data) { + char len_str[32]; + int n = snprintf(len_str, sizeof(len_str), "%d", http_head->data_len); + if (n < 0 || n >= (int)sizeof(len_str)) { + return -1; + } + + if (buffer_len - offset < 2) { + return -1; + } + buffer[offset++] = 0x5C; /* 01011100 - indexed name content-length (index 28) */ + offset_ret = _hpack_encode_string(len_str, strlen(len_str), buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + } + + return offset; +} + +/* Parse HPACK header block */ +static int _http2_parse_header_block(struct http_head *http_head, const uint8_t *data, int data_len) +{ + int offset = 0; + int offset_ret = 0; + + while (offset < data_len) { + uint8_t b = data[offset]; + const char *name = NULL; + const char *value = NULL; + char *buffer_name = NULL; + char *buffer_value = NULL; + size_t str_len = 0; + int buffer_left_len = 0; + struct hpack_header_field *header = NULL; + + if (b & 0x80) { + /* Indexed Header Field */ + uint32_t index = 0; + offset_ret = _hpack_decode_integer(data + offset, data_len - offset, 7, &index); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + header = hpack_get_static_header_field(index); + if (header == NULL) { + return -2; + } + + name = header->name; + value = header->value; + + } else if (b & 0x40) { + /* Literal Header Field with Incremental Indexing */ + uint32_t index = 0; + offset_ret = _hpack_decode_integer(data + offset, data_len - offset, 6, &index); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + if (index == 0) { + /* New name */ + buffer_name = (char *)_http_head_buffer_get_end(http_head); + buffer_left_len = _http_head_buffer_left_len(http_head); + + offset_ret = + _hpack_decode_string(data + offset, data_len - offset, buffer_name, buffer_left_len - 1, &str_len); + if (offset_ret < 0) { + return offset_ret; + } + offset += offset_ret; + + if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { + return -3; + } + name = buffer_name; + } else { + /* Indexed name */ + header = hpack_get_static_header_field(index); + if (header == NULL) { + return -2; + } + name = header->name; + } + + /* Value string */ + buffer_value = (char *)_http_head_buffer_get_end(http_head); + buffer_left_len = _http_head_buffer_left_len(http_head); + + offset_ret = + _hpack_decode_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1, &str_len); + if (offset_ret < 0) { + return offset_ret; + } + offset += offset_ret; + + if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { + return -3; + } + value = buffer_value; + + } else if ((b & 0xF0) == 0x00) { + /* Literal Header Field without Indexing */ + uint32_t index = 0; + offset_ret = _hpack_decode_integer(data + offset, data_len - offset, 4, &index); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + if (index == 0) { + /* New name */ + buffer_name = (char *)_http_head_buffer_get_end(http_head); + buffer_left_len = _http_head_buffer_left_len(http_head); + + offset_ret = + _hpack_decode_string(data + offset, data_len - offset, buffer_name, buffer_left_len - 1, &str_len); + if (offset_ret < 0) { + return offset_ret; + } + offset += offset_ret; + + if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { + return -3; + } + name = buffer_name; + } else { + /* Indexed name */ + header = hpack_get_static_header_field(index); + if (header == NULL) { + return -2; + } + name = header->name; + } + + /* Value string */ + buffer_value = (char *)_http_head_buffer_get_end(http_head); + buffer_left_len = _http_head_buffer_left_len(http_head); + + offset_ret = + _hpack_decode_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1, &str_len); + if (offset_ret < 0) { + return offset_ret; + } + offset += offset_ret; + + if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { + return -3; + } + value = buffer_value; + + } else { + /* Other types not supported */ + return -2; + } + + if (http_head_add_fields(http_head, name, value) != 0) { + return -2; + } + } + + return 0; +} + +/* Setup HTTP code and method from pseudo-headers */ +static int _http_head_setup_http2_0(struct http_head *http_head) +{ + const char *status = NULL; + const char *method = NULL; + const char *path = NULL; + + method = http_head_get_fields_value(http_head, ":method"); + if (method) { + http_head->head_type = HTTP_HEAD_REQUEST; + if (strcmp(method, "GET") == 0) { + http_head->method = HTTP_METHOD_GET; + } else if (strcmp(method, "POST") == 0) { + http_head->method = HTTP_METHOD_POST; + } else if (strcmp(method, "PUT") == 0) { + http_head->method = HTTP_METHOD_PUT; + } else if (strcmp(method, "DELETE") == 0) { + http_head->method = HTTP_METHOD_DELETE; + } + + path = http_head_get_fields_value(http_head, ":path"); + if (path) { + char *path_copy = (char *)_http_head_buffer_get_end(http_head); + int path_len = strlen(path); + if (_http_head_buffer_left_len(http_head) < path_len + 1) { + return -3; + } + memcpy(path_copy, path, path_len + 1); + _http_head_buffer_append(http_head, NULL, path_len + 1); + http_head->url = path_copy; + + /* Parse query parameters */ + _http_head_parse_params(http_head, path_copy, path_len); + } + } + + status = http_head_get_fields_value(http_head, ":status"); + if (status) { + http_head->head_type = HTTP_HEAD_RESPONSE; + http_head->code = atoi(status); + } + + return 0; +} int http_head_parse_http2_0(struct http_head *http_head, const uint8_t *data, int data_len) { - return -2; + int offset = 0; + const uint8_t *frame_data = NULL; + int frame_data_len = 0; + int headers_complete = 0; + int data_complete = 0; + const uint8_t *body_data = NULL; + int body_data_len = 0; + + /* Parse HTTP/2 frames */ + while (offset < data_len && (!headers_complete || !data_complete)) { + if (data_len - offset < HTTP2_FRAME_HEADER_SIZE) { + return -1; /* Incomplete frame header */ + } + + /* Parse frame header (9 bytes) */ + uint32_t frame_length = ((uint32_t)data[offset] << 16) | ((uint32_t)data[offset + 1] << 8) | data[offset + 2]; + uint8_t frame_type = data[offset + 3]; + uint8_t frame_flags = data[offset + 4]; + uint32_t stream_id = ((uint32_t)data[offset + 5] << 24) | ((uint32_t)data[offset + 6] << 16) | + ((uint32_t)data[offset + 7] << 8) | data[offset + 8]; + stream_id &= 0x7FFFFFFF; /* Clear reserved bit */ + + offset += HTTP2_FRAME_HEADER_SIZE; + + if (data_len - offset < (int)frame_length) { + return -1; /* Incomplete frame payload */ + } + + frame_data = data + offset; + frame_data_len = frame_length; + offset += frame_length; + + /* Process frame based on type */ + if (frame_type == HTTP2_FRAME_HEADERS) { + int header_offset = 0; + + /* Skip padding if present */ + if (frame_flags & HTTP2_FLAG_PADDED) { + if (frame_data_len < 1) { + return -1; + } + uint8_t pad_length = frame_data[0]; + /* Validate padding before subtraction to prevent underflow */ + if (frame_data_len < 1 + pad_length) { + return -1; + } + header_offset = 1; + frame_data_len -= (1 + pad_length); + } + + /* Skip priority if present */ + if (frame_flags & HTTP2_FLAG_PRIORITY) { + if (frame_data_len < 5) { + return -1; + } + header_offset += 5; + frame_data_len -= 5; + } + + /* Parse header block */ + if (_http2_parse_header_block(http_head, frame_data + header_offset, frame_data_len) < 0) { + return -2; + } + + if (frame_flags & HTTP2_FLAG_END_HEADERS) { + headers_complete = 1; + } + + if (frame_flags & HTTP2_FLAG_END_STREAM) { + data_complete = 1; + } + + } else if (frame_type == HTTP2_FRAME_DATA) { + int data_offset = 0; + + /* Skip padding if present */ + if (frame_flags & HTTP2_FLAG_PADDED) { + if (frame_data_len < 1) { + return -1; + } + uint8_t pad_length = frame_data[0]; + /* Validate padding before subtraction to prevent underflow */ + if (frame_data_len < 1 + pad_length) { + return -1; + } + data_offset = 1; + frame_data_len -= (1 + pad_length); + } + + /* Store data */ + body_data = frame_data + data_offset; + body_data_len = frame_data_len; + + if (frame_flags & HTTP2_FLAG_END_STREAM) { + data_complete = 1; + } + + } else if (frame_type == HTTP2_FRAME_CONTINUATION) { + /* Continue parsing header block */ + if (_http2_parse_header_block(http_head, frame_data, frame_data_len) < 0) { + return -2; + } + + if (frame_flags & HTTP2_FLAG_END_HEADERS) { + headers_complete = 1; + } + } + /* Ignore other frame types */ + } + + if (!headers_complete) { + return -1; + } + + /* Setup HTTP code and method from pseudo-headers */ + if (_http_head_setup_http2_0(http_head) != 0) { + return -2; + } + + /* Set body data if present */ + if (body_data && body_data_len > 0) { + http_head->data = body_data; + http_head->data_len = body_data_len; + } + + http_head->head_ok = 1; + + return offset; } int http_head_serialize_http2_0(struct http_head *http_head, uint8_t *buffer, int buffer_len) { - return -2; -} \ No newline at end of file + int offset = 0; + int header_block_len = 0; + uint8_t header_block[4096]; + + /* Build HPACK header block */ + header_block_len = _http2_build_header_block(http_head, header_block, sizeof(header_block)); + if (header_block_len < 0) { + return -1; + } + + /* Write HEADERS frame */ + uint32_t frame_length = header_block_len; + uint8_t frame_flags = HTTP2_FLAG_END_HEADERS; + uint32_t stream_id = 1; /* Use stream ID 1 for DoH requests */ + + if (http_head->data_len == 0 || http_head->data == NULL) { + frame_flags |= HTTP2_FLAG_END_STREAM; + } + + if (buffer_len - offset < HTTP2_FRAME_HEADER_SIZE + (int)frame_length) { + return -1; + } + + /* Frame header */ + buffer[offset++] = (frame_length >> 16) & 0xFF; + buffer[offset++] = (frame_length >> 8) & 0xFF; + buffer[offset++] = frame_length & 0xFF; + buffer[offset++] = HTTP2_FRAME_HEADERS; + buffer[offset++] = frame_flags; + buffer[offset++] = (stream_id >> 24) & 0xFF; + buffer[offset++] = (stream_id >> 16) & 0xFF; + buffer[offset++] = (stream_id >> 8) & 0xFF; + buffer[offset++] = stream_id & 0xFF; + + /* Frame payload */ + memcpy(buffer + offset, header_block, header_block_len); + offset += header_block_len; + + /* Write DATA frame if present */ + if (http_head->data_len > 0 && http_head->data) { + frame_length = http_head->data_len; + frame_flags = HTTP2_FLAG_END_STREAM; + + if (buffer_len - offset < HTTP2_FRAME_HEADER_SIZE + (int)frame_length) { + return -1; + } + + /* Frame header */ + buffer[offset++] = (frame_length >> 16) & 0xFF; + buffer[offset++] = (frame_length >> 8) & 0xFF; + buffer[offset++] = frame_length & 0xFF; + buffer[offset++] = HTTP2_FRAME_DATA; + buffer[offset++] = frame_flags; + buffer[offset++] = (stream_id >> 24) & 0xFF; + buffer[offset++] = (stream_id >> 16) & 0xFF; + buffer[offset++] = (stream_id >> 8) & 0xFF; + buffer[offset++] = stream_id & 0xFF; + + /* Frame payload */ + memcpy(buffer + offset, http_head->data, http_head->data_len); + offset += http_head->data_len; + } + + return offset; +} +/* HTTP/2 connection preface (RFC 7540 Section 3.5) */ +static const unsigned char HTTP2_PREFACE[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +#define HTTP2_PREFACE_LEN 24 + +/* Initialize HTTP/2 context */ +struct http2_context *http2_context_init(int is_server) +{ +struct http2_context *ctx = NULL; + +ctx = malloc(sizeof(struct http2_context)); +if (ctx == NULL) { +return NULL; +} + +memset(ctx, 0, sizeof(struct http2_context)); + +/* Initialize stream ID: odd for client, even for server */ +ctx->next_stream_id = is_server ? 2 : 1; + +/* Default SETTINGS */ +ctx->max_concurrent_streams = 100; +ctx->initial_window_size = 65535; +ctx->max_frame_size = 16384; + +/* Initialize stream list */ +INIT_LIST_HEAD(&ctx->stream_list); +ctx->stream_count = 0; + +/* Allocate connection buffer for incomplete frames */ +ctx->conn_buffer_size = 32768; +ctx->conn_buffer = malloc(ctx->conn_buffer_size); +if (ctx->conn_buffer == NULL) { +free(ctx); +return NULL; +} +ctx->conn_buffer_len = 0; + +ctx->initialized = 1; + +return ctx; +} + +/* Destroy HTTP/2 context */ +void http2_context_destroy(struct http2_context *ctx) +{ +struct http2_stream *stream = NULL; +struct http2_stream *tmp = NULL; + +if (ctx == NULL) { +return; +} + +/* Free all streams */ +list_for_each_entry_safe(stream, tmp, &ctx->stream_list, list) +{ +list_del(&stream->list); +if (stream->recv_buffer) { +free(stream->recv_buffer); +} +free(stream); +} + +/* Free connection buffer */ +if (ctx->conn_buffer) { +free(ctx->conn_buffer); +} + +free(ctx); +} + +/* Create a new stream */ +struct http2_stream *http2_stream_create(struct http2_context *ctx) +{ +struct http2_stream *stream = NULL; + +if (ctx == NULL || !ctx->initialized) { +return NULL; +} + +stream = malloc(sizeof(struct http2_stream)); +if (stream == NULL) { +return NULL; +} + +memset(stream, 0, sizeof(struct http2_stream)); + +/* Assign stream ID */ +stream->stream_id = ctx->next_stream_id; +ctx->next_stream_id += 2; /* Skip to next odd/even number */ + +stream->state = HTTP2_STREAM_IDLE; + +/* Allocate receive buffer */ +stream->recv_buffer_size = 65536; +stream->recv_buffer = malloc(stream->recv_buffer_size); +if (stream->recv_buffer == NULL) { +free(stream); +return NULL; +} +stream->recv_buffer_len = 0; + +stream->create_time = time(NULL); + +/* Add to stream list */ +list_add_tail(&stream->list, &ctx->stream_list); +ctx->stream_count++; + +return stream; +} + +/* Find stream by ID */ +struct http2_stream *http2_stream_find(struct http2_context *ctx, uint32_t stream_id) +{ +struct http2_stream *stream = NULL; + +if (ctx == NULL) { +return NULL; +} + +list_for_each_entry(stream, &ctx->stream_list, list) +{ +if (stream->stream_id == stream_id) { +return stream; +} +} + +return NULL; +} + +/* Close and destroy a stream */ +void http2_stream_close(struct http2_context *ctx, struct http2_stream *stream) +{ +if (ctx == NULL || stream == NULL) { +return; +} + +/* Remove from list */ +list_del(&stream->list); +ctx->stream_count--; + +/* Free buffers */ +if (stream->recv_buffer) { +free(stream->recv_buffer); +} + +free(stream); +} + +/* Send HTTP/2 connection preface */ +int http2_send_preface(unsigned char *buffer, int buffer_size) +{ +int offset = 0; + +if (buffer_size < HTTP2_PREFACE_LEN + HTTP2_FRAME_HEADER_SIZE) { +return -1; +} + +/* Send HTTP/2 connection preface */ +memcpy(buffer, HTTP2_PREFACE, HTTP2_PREFACE_LEN); +offset = HTTP2_PREFACE_LEN; + +/* Send empty SETTINGS frame */ +/* Frame length: 0 */ +buffer[offset++] = 0x00; +buffer[offset++] = 0x00; +buffer[offset++] = 0x00; +/* Frame type: SETTINGS */ +buffer[offset++] = HTTP2_FRAME_SETTINGS; +/* Flags: 0 */ +buffer[offset++] = 0x00; +/* Stream ID: 0 */ +buffer[offset++] = 0x00; +buffer[offset++] = 0x00; +buffer[offset++] = 0x00; +buffer[offset++] = 0x00; + +return offset; +} + +/* HTTP/2 handshake - prepare connection */ +int http2_handshake(struct http2_context *ctx, unsigned char *buffer, int buffer_size) +{ +if (ctx == NULL || !ctx->initialized) { +return -1; +} + +/* Generate preface and SETTINGS */ +return http2_send_preface(buffer, buffer_size); +} + +/* HTTP/2 poll - get an available stream for communication */ +int http2_poll(struct http2_context *ctx, struct http2_stream **available_stream) +{ +struct http2_stream *stream = NULL; + +if (ctx == NULL || !ctx->initialized || available_stream == NULL) { +return -1; +} + +*available_stream = NULL; + +/* Find an OPEN stream or create a new one */ +list_for_each_entry(stream, &ctx->stream_list, list) +{ +if (stream->state == HTTP2_STREAM_OPEN || stream->state == HTTP2_STREAM_IDLE) { +*available_stream = stream; +return 0; +} +} + +/* No available stream found, create a new one */ +stream = http2_stream_create(ctx); +if (stream == NULL) { +return -1; +} + +*available_stream = stream; +return 0; +} diff --git a/src/http_parse/http2_parse.h b/src/http_parse/http2_parse.h index b55a09a41e..a7bfd6e43a 100644 --- a/src/http_parse/http2_parse.h +++ b/src/http_parse/http2_parse.h @@ -20,15 +20,84 @@ #define _HTTP_PARSE_HTTP2_H_ #include "http_parse.h" +#include +#include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ +/* HTTP/2 Stream State */ +typedef enum { + HTTP2_STREAM_IDLE = 0, + HTTP2_STREAM_OPEN, + HTTP2_STREAM_HALF_CLOSED_LOCAL, + HTTP2_STREAM_HALF_CLOSED_REMOTE, + HTTP2_STREAM_CLOSED +} http2_stream_state_t; + +/* HTTP/2 Stream */ +struct http2_stream { + struct list_head list; + uint32_t stream_id; + http2_stream_state_t state; + + /* Stream buffers */ + unsigned char *recv_buffer; + int recv_buffer_size; + int recv_buffer_len; + + /* Request tracking */ + void *request_data; + int request_data_len; + time_t create_time; +}; + +/* HTTP/2 Connection Context */ +struct http2_context { + /* Connection state */ + int initialized; + uint32_t next_stream_id; /* Next stream ID to use (odd for client, even for server) */ + + /* SETTINGS */ + uint32_t max_concurrent_streams; + uint32_t initial_window_size; + uint32_t max_frame_size; + + /* Streams */ + struct list_head stream_list; + int stream_count; + + /* Connection buffer for incomplete frames */ + unsigned char *conn_buffer; + int conn_buffer_size; + int conn_buffer_len; +}; + +/* HTTP/2 Frame Parsing */ int http_head_parse_http2_0(struct http_head *http_head, const uint8_t *data, int data_len); int http_head_serialize_http2_0(struct http_head *http_head, uint8_t *buffer, int buffer_len); +/* HTTP/2 Connection Context Management */ +struct http2_context *http2_context_init(int is_server); + +void http2_context_destroy(struct http2_context *ctx); + +/* HTTP/2 Stream Management */ +struct http2_stream *http2_stream_create(struct http2_context *ctx); + +struct http2_stream *http2_stream_find(struct http2_context *ctx, uint32_t stream_id); + +void http2_stream_close(struct http2_context *ctx, struct http2_stream *stream); + +/* HTTP/2 Protocol Operations */ +int http2_handshake(struct http2_context *ctx, unsigned char *buffer, int buffer_size); + +int http2_poll(struct http2_context *ctx, struct http2_stream **available_stream); + +int http2_send_preface(unsigned char *buffer, int buffer_size); + #ifdef __cplusplus } #endif /*__cplusplus */ diff --git a/test/cases/test-bind.cc b/test/cases/test-bind.cc index 769b754714..f97835a586 100644 --- a/test/cases/test-bind.cc +++ b/test/cases/test-bind.cc @@ -74,6 +74,30 @@ address /example.com/1.2.3.4 EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } +TEST(Bind, https_http2) +{ + Defer + { + unlink("/tmp/smartdns-cert.pem"); + unlink("/tmp/smartdns-key.pem"); + }; + + smartdns::Server server_wrap; + smartdns::Server server; + + server.Start(R"""(bind [::]:61053 +server https://127.0.0.1:60053 -no-check-certificate +)"""); + server_wrap.Start(R"""(bind-https [::]:60053 +address /example.com/1.2.3.4 +)"""); + smartdns::Client client; + ASSERT_TRUE(client.Query("example.com", 61053)); + ASSERT_EQ(client.GetAnswerNum(), 1); + EXPECT_EQ(client.GetStatus(), "NOERROR"); + EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); +} + TEST(Bind, udp_tcp) { smartdns::MockServer server_upstream; diff --git a/test/cases/test-http.cc b/test/cases/test-http.cc index 497c23f966..43defcad9b 100644 --- a/test/cases/test-http.cc +++ b/test/cases/test-http.cc @@ -222,3 +222,108 @@ TEST_F(HTTP, http3_small_buffer) EXPECT_GT(ret, 0); http_head_destroy(http_head); } + +TEST_F(HTTP, http2_0_request_serialize_parse) +{ + /* Test HTTP/2 request serialization and parsing */ + struct http_head *http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + http_head_set_method(http_head, HTTP_METHOD_POST); + http_head_set_url(http_head, "/dns-query"); + http_head_add_fields(http_head, ":authority", "dns.example.com"); + http_head_add_fields(http_head, "content-type", "application/dns-message"); + http_head_add_fields(http_head, "accept", "application/dns-message"); + http_head_add_fields(http_head, "user-agent", "smartdns"); + const char *dns_data = "DNS_QUERY_DATA"; + http_head_set_data(http_head, dns_data, strlen(dns_data)); + http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); + + unsigned char buffer[2048]; + int ret = http_head_serialize(http_head, buffer, 2048); + ASSERT_GT(ret, 0); + std::cout << "HTTP/2 request serialized, size: " << ret << std::endl; + + http_head_destroy(http_head); + + /* Parse the serialized data */ + http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + ret = http_head_parse(http_head, buffer, ret); + ASSERT_GT(ret, 0); + EXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_POST); + EXPECT_STREQ(http_head_get_url(http_head), "/dns-query"); + EXPECT_STREQ(http_head_get_fields_value(http_head, ":authority"), "dns.example.com"); + EXPECT_STREQ(http_head_get_fields_value(http_head, "content-type"), "application/dns-message"); + EXPECT_STREQ(http_head_get_fields_value(http_head, "accept"), "application/dns-message"); + EXPECT_STREQ(http_head_get_fields_value(http_head, "user-agent"), "smartdns"); + ASSERT_NE(http_head_get_data(http_head), nullptr); + EXPECT_EQ(http_head_get_data_len(http_head), (int)strlen(dns_data)); + EXPECT_EQ(memcmp(http_head_get_data(http_head), dns_data, strlen(dns_data)), 0); + + http_head_destroy(http_head); +} + +TEST_F(HTTP, http2_0_response_serialize_parse) +{ + /* Test HTTP/2 response serialization and parsing */ + struct http_head *http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + http_head_set_httpcode(http_head, 200, "OK"); + http_head_add_fields(http_head, "content-type", "application/dns-message"); + const char *dns_response = "DNS_RESPONSE_DATA"; + http_head_set_data(http_head, dns_response, strlen(dns_response)); + http_head_set_head_type(http_head, HTTP_HEAD_RESPONSE); + + unsigned char buffer[2048]; + int ret = http_head_serialize(http_head, buffer, 2048); + ASSERT_GT(ret, 0); + std::cout << "HTTP/2 response serialized, size: " << ret << std::endl; + + http_head_destroy(http_head); + + /* Parse the serialized data */ + http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + ret = http_head_parse(http_head, buffer, ret); + ASSERT_GT(ret, 0); + EXPECT_EQ(http_head_get_httpcode(http_head), 200); + EXPECT_STREQ(http_head_get_fields_value(http_head, "content-type"), "application/dns-message"); + ASSERT_NE(http_head_get_data(http_head), nullptr); + EXPECT_EQ(http_head_get_data_len(http_head), (int)strlen(dns_response)); + EXPECT_EQ(memcmp(http_head_get_data(http_head), dns_response, strlen(dns_response)), 0); + + http_head_destroy(http_head); +} + +TEST_F(HTTP, http2_0_get_request) +{ + /* Test HTTP/2 GET request */ + struct http_head *http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + http_head_set_method(http_head, HTTP_METHOD_GET); + http_head_set_url(http_head, "/"); + http_head_add_fields(http_head, ":authority", "example.com"); + http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); + + unsigned char buffer[2048]; + int ret = http_head_serialize(http_head, buffer, 2048); + ASSERT_GT(ret, 0); + + http_head_destroy(http_head); + + /* Parse back */ + http_head = http_head_init(2048, HTTP_VERSION_2_0); + ASSERT_NE(http_head, nullptr); + + ret = http_head_parse(http_head, buffer, ret); + ASSERT_GT(ret, 0); + EXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_GET); + EXPECT_STREQ(http_head_get_url(http_head), "/"); + + http_head_destroy(http_head); +}