Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ package/target
package/*.gz
package/*.ipk
target
_codeql_detected_source_root
281 changes: 281 additions & 0 deletions src/dns_client/client_http2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*************************************************************************
*
* 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"

/* Include http2_parse header from http_parse directory */
#include "../http_parse/http2_parse.h"

#include <stdlib.h>
#include <string.h>

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;
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;
}
41 changes: 41 additions & 0 deletions src/dns_client/client_http2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*************************************************************************
*
* 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"
#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
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
Loading
Loading