Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _codeql_detected_source_root
229 changes: 229 additions & 0 deletions src/dns_client/client_http2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*************************************************************************
*
* Copyright (C) 2018-2025 Ruilin Peng (Nick) <[email protected]>.
*
* 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 <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE

#include "client_http2.h"
#include "client_socket.h"
#include "client_tls.h"
#include "server_info.h"

#include "smartdns/http_parse.h"

/* 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";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http2相关的函数,变量,定义移动到http2的源码文件中

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 51100d0. Moved HTTP/2 context management, stream functions, handshake, and poll functions from client_http2.c to http2_parse.c. The client file is now a thin wrapper.

#define HTTP2_PREFACE_LEN 24

/* HTTP/2 Frame Header Size */
#define HTTP2_FRAME_HEADER_SIZE 9

/* HTTP/2 Frame Types */
#define HTTP2_FRAME_SETTINGS 0x4

int _dns_client_send_http2_preface(struct dns_server_info *server_info)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这些都应该放到http2的源码文件中

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 51100d0. All HTTP/2 protocol code (context, streams, handshake, poll) now in http2_parse.c/h.

{
unsigned char buffer[256];
int offset = 0;
int send_len = 0;

/* 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;

/* 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;

if (len > sizeof(inpacket_data) - 512) {
tlog(TLOG_ERROR, "packet size is invalid.");
return -1;
}

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.");
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.");
return -1;
}

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;
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);
}
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);
}

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 ret = -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);
int dns_packet_len = http_head_get_data_len(http_head);
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable declaration should be at the beginning of the block scope to maintain C89/C90 compatibility, which this codebase appears to follow based on other files. Move the declaration of dns_packet_len to the beginning of the function alongside other variable declarations.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 92d3c26. Moved dns_packet_len declaration to function start for C89 compatibility.

Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable declaration should be moved to the beginning of the block for C89/C90 compatibility. Declare dns_packet_len at line 159 with other variables.

Copilot uses AI. Check for mistakes.

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;
}

/* 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;
}
40 changes: 40 additions & 0 deletions src/dns_client/client_http2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*************************************************************************
*
* Copyright (C) 2018-2025 Ruilin Peng (Nick) <[email protected]>.
*
* 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 <http://www.gnu.org/licenses/>.
*/

#ifndef _DNS_CLIENT_HTTP2_H_
#define _DNS_CLIENT_HTTP2_H_

#include "dns_client.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
35 changes: 35 additions & 0 deletions src/dns_client/client_https.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/dns_client/client_https.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
14 changes: 13 additions & 1 deletion src/dns_client/client_tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,19 @@ 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" */
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') {
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The protocol detection logic relies on a single byte check which may be fragile. Consider adding a comment explaining why checking for 'H' is sufficient, or implement a more robust check (e.g., verifying the frame type byte is valid: data[3] <= HTTP2_FRAME_CONTINUATION).

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment in commit 92d3c26 explaining the heuristic: HTTP/2 frames never start with 'H' (0x48) as the first byte is part of the 24-bit length field. This works reliably for distinguishing HTTP/1.1 ("HTTP/1.1...") from HTTP/2 frame headers.

http_version = HTTP_VERSION_2_0;
}
Comment on lines 160 to 169
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP version detection is unreliable. An HTTP/2 SETTINGS or WINDOW_UPDATE frame could start with a non-zero byte that happens to not be 'H', leading to false positives. Use the ALPN negotiation result stored in the server_info structure instead of heuristic detection based on frame content.

Suggested change
/* 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" */
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;
}
/* Use ALPN negotiation result to determine HTTP version */
if (server_info->alpn_result == ALPN_HTTP2) {
http_version = HTTP_VERSION_2_0;
} else {
http_version = HTTP_VERSION_1_1;

Copilot uses AI. Check for mistakes.
}

http_head = http_head_init(8192, http_version);
if (http_head == NULL) {
goto out;
}
Expand Down
7 changes: 7 additions & 0 deletions src/dns_client/client_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading