Skip to content

Commit 6c8773a

Browse files
committed
feat: support compressed http3 bodies
1 parent 242d4e1 commit 6c8773a

7 files changed

Lines changed: 239 additions & 6 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ $(SMARTDNS_SYSTEMD): systemd/smartdns.service.in
5555
help:
5656
@echo "Options:"
5757
@echo " WITH_UI=1: Build with smartdns-ui plugin"
58+
@echo " WITH_ZLIB=auto|1|0: Auto-detect, force enable, or disable zlib support"
5859
@echo " OPTIMIZE_SIZE=1: Optimize size of the smartdns-ui plugin (only for smartdns-ui)"
5960
@echo " DESTDIR: Specify the installation directory prefix"
6061

@@ -70,4 +71,3 @@ install: SMARTDNS_BIN
7071
install -v -m 0755 -D -t $(DESTDIR)$(SBINDIR) src/smartdns
7172
install -v -m 0644 -D -t $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) systemd/smartdns.service
7273
$(call PLUGINS_TARGETS, install)
73-

plugin/smartdns-ui/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ else
103103
SMARTDNS_BUILD_TYPE=
104104
endif
105105

106-
.PHONY: all clean install $(BIN)
106+
.PHONY: all clean install test test-prepare $(BIN)
107107

108108
all: $(BIN)
109109

110110
test-prepare:
111-
$(MAKE) -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a
111+
$(MAKE) WITH_ZLIB=0 -B -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a
112112

113113
$(BIN):
114114
CXXFLAGS= CFLAGS= MAKEFLAGS= RUSTFLAGS="$(CARGO_RUSTFLAGS) $(RUSTFLAGS)" $(CARGO_BUILD_ENV) cargo build $(CARGO_BUILD_ARGS) --features "build-release"
@@ -118,7 +118,7 @@ install: $(BIN)
118118
install -v -m 0644 -D -t $(DESTDIR)$(SLIBDIR)/smartdns target/smartdns_ui.so
119119

120120
test: test-prepare
121-
MAKEFLAGS= cargo test
121+
MAKEFLAGS= WITH_ZLIB=0 cargo test
122122

123123
clean:
124124
cargo clean

plugin/smartdns-ui/build.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ fn link_rename_lib() {
4949
println!("cargo:rustc-link-arg={}", so_path);
5050
}
5151

52+
fn should_link_zlib() -> bool {
53+
matches!(
54+
env::var("WITH_ZLIB")
55+
.unwrap_or_default()
56+
.to_lowercase()
57+
.as_str(),
58+
"1" | "yes" | "true" | "on"
59+
)
60+
}
61+
5262
fn link_smartdns_lib() {
5363
let curr_source_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
5464
let smartdns_src_dir = format!("{}/../../src", curr_source_dir);
@@ -95,6 +105,10 @@ fn link_smartdns_lib() {
95105
println!("cargo:rustc-link-lib=static=smartdns-test");
96106
println!("cargo:rustc-link-lib=ssl");
97107
println!("cargo:rustc-link-lib=crypto");
108+
println!("cargo:rerun-if-env-changed=WITH_ZLIB");
109+
if should_link_zlib() {
110+
println!("cargo:rustc-link-lib=z");
111+
}
98112
println!("cargo:rustc-link-search=native={}", smartdns_src_dir);
99113
}
100114
}

src/Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ ifeq ($(HAS_UNWIND), 1)
4343
override CFLAGS += -DHAVE_UNWIND_BACKTRACE
4444
endif
4545

46+
WITH_ZLIB ?= auto
47+
HAS_ZLIB := $(shell printf '$(HASH)include <zlib.h>\nint main(void) { return zlibVersion()[0] == 0; }' | $(CC) -x c - -lz -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0)
48+
ifeq ($(WITH_ZLIB), yes)
49+
override WITH_ZLIB := 1
50+
endif
51+
ifeq ($(WITH_ZLIB), auto)
52+
ifeq ($(HAS_ZLIB), 1)
53+
override CFLAGS += -DWITH_ZLIB
54+
override LDFLAGS += -lz
55+
endif
56+
else ifeq ($(WITH_ZLIB), 1)
57+
override CFLAGS += -DWITH_ZLIB
58+
override LDFLAGS += -lz
59+
endif
60+
4661
override CFLAGS +=-Iinclude
4762
override CFLAGS += -DBASE_FILE_NAME='"$(notdir $<)"'
4863
override CFLAGS += $(EXTRA_CFLAGS)
@@ -118,4 +133,3 @@ clean:
118133
lint-files:
119134
@echo "Specify FILES variable to lint specific files, e.g., make lint-files FILES='src/dns.c src/proxy.c'"
120135
clang-tidy -p=. $(FILES) -- $(CFLAGS)
121-

src/http_parse/http3_parse.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,16 @@
2323
#include <limits.h>
2424
#include <stdio.h>
2525
#include <stdlib.h>
26+
#include <string.h>
27+
#include <strings.h>
28+
29+
#ifdef WITH_ZLIB
30+
#include <zlib.h>
31+
#endif
2632

2733
#define HTTP3_HEADER_FRAME 1
2834
#define HTTP3_DATA_FRAME 0
35+
#define HTTP3_MAX_DECOMPRESSED_SIZE (1024 * 1024)
2936

3037
static int _http3_get_expected_data_len(struct http_head *http_head, int *expect_data_len)
3138
{
@@ -56,6 +63,148 @@ static int _http3_get_expected_data_len(struct http_head *http_head, int *expect
5663
return 0;
5764
}
5865

66+
static int _http3_decompress_data(const uint8_t *compressed, int compressed_len, uint8_t **decompressed,
67+
int *decompressed_len, int is_gzip)
68+
{
69+
#ifdef WITH_ZLIB
70+
z_stream strm;
71+
int ret = Z_OK;
72+
int window_bits = is_gzip ? (15 + 16) : 15;
73+
int out_size = compressed_len * 4;
74+
uint8_t *out_buf = NULL;
75+
76+
if (compressed == NULL || compressed_len <= 0 || decompressed == NULL || decompressed_len == NULL) {
77+
return -2;
78+
}
79+
80+
if (out_size < 8192) {
81+
out_size = 8192;
82+
}
83+
if (out_size > HTTP3_MAX_DECOMPRESSED_SIZE) {
84+
out_size = HTTP3_MAX_DECOMPRESSED_SIZE;
85+
}
86+
87+
out_buf = malloc(out_size);
88+
if (out_buf == NULL) {
89+
return -3;
90+
}
91+
92+
memset(&strm, 0, sizeof(strm));
93+
strm.avail_in = compressed_len;
94+
strm.next_in = (Bytef *)compressed;
95+
96+
ret = inflateInit2(&strm, window_bits);
97+
if (ret != Z_OK) {
98+
free(out_buf);
99+
return -2;
100+
}
101+
102+
while (ret != Z_STREAM_END) {
103+
if ((int)strm.total_out == out_size) {
104+
int new_size = out_size * 2;
105+
uint8_t *new_buf = NULL;
106+
107+
if (new_size <= out_size || new_size > HTTP3_MAX_DECOMPRESSED_SIZE) {
108+
inflateEnd(&strm);
109+
free(out_buf);
110+
return -3;
111+
}
112+
113+
new_buf = realloc(out_buf, new_size);
114+
if (new_buf == NULL) {
115+
inflateEnd(&strm);
116+
free(out_buf);
117+
return -3;
118+
}
119+
120+
out_buf = new_buf;
121+
out_size = new_size;
122+
}
123+
124+
strm.avail_out = out_size - strm.total_out;
125+
strm.next_out = out_buf + strm.total_out;
126+
127+
ret = inflate(&strm, Z_NO_FLUSH);
128+
if (ret == Z_STREAM_END) {
129+
break;
130+
}
131+
132+
if (ret == Z_BUF_ERROR || (ret == Z_OK && strm.avail_in == 0 && strm.avail_out > 0)) {
133+
inflateEnd(&strm);
134+
free(out_buf);
135+
return -1;
136+
}
137+
138+
if (ret != Z_OK) {
139+
inflateEnd(&strm);
140+
free(out_buf);
141+
return -2;
142+
}
143+
}
144+
145+
*decompressed = out_buf;
146+
*decompressed_len = strm.total_out;
147+
inflateEnd(&strm);
148+
return 0;
149+
#else
150+
return -2;
151+
#endif
152+
}
153+
154+
static int _http3_decode_content_encoding(struct http_head *http_head)
155+
{
156+
const char *content_encoding = NULL;
157+
uint8_t *decompressed = NULL;
158+
int decompressed_len = 0;
159+
int is_gzip = 0;
160+
int ret = 0;
161+
int data_offset = 0;
162+
int data_end_offset = 0;
163+
164+
if (http_head == NULL || http_head->data == NULL || http_head->data_len <= 0) {
165+
return 0;
166+
}
167+
168+
content_encoding = http_head_get_fields_value(http_head, "content-encoding");
169+
if (content_encoding == NULL || content_encoding[0] == '\0') {
170+
return 0;
171+
}
172+
173+
is_gzip = strcasecmp(content_encoding, "gzip") == 0;
174+
if (!is_gzip && strcasecmp(content_encoding, "deflate") != 0) {
175+
return 0;
176+
}
177+
178+
ret = _http3_decompress_data(http_head->data, http_head->data_len, &decompressed, &decompressed_len, is_gzip);
179+
if (ret != 0) {
180+
return ret;
181+
}
182+
183+
data_offset = http_head->data - http_head->buff;
184+
data_end_offset = data_offset + http_head->data_len;
185+
if (data_offset < 0 || data_offset > http_head->buff_size || decompressed_len > http_head->buff_size - data_offset) {
186+
free(decompressed);
187+
return -3;
188+
}
189+
190+
if (data_end_offset == http_head->buff_len) {
191+
memcpy(http_head->buff + data_offset, decompressed, decompressed_len);
192+
http_head->buff_len = data_offset + decompressed_len;
193+
http_head->data = http_head->buff + data_offset;
194+
} else {
195+
if (_http_head_buffer_left_len(http_head) < decompressed_len) {
196+
free(decompressed);
197+
return -3;
198+
}
199+
http_head->data = _http_head_buffer_get_end(http_head);
200+
_http_head_buffer_append(http_head, decompressed, decompressed_len);
201+
}
202+
http_head->data_len = decompressed_len;
203+
204+
free(decompressed);
205+
return 0;
206+
}
207+
59208
static int _quicvarint_encode(uint64_t value, uint8_t *buffer, int buffer_size)
60209
{
61210
if (value <= 63) {
@@ -660,6 +809,11 @@ int http_head_parse_http3_0(struct http_head *http_head, const uint8_t *data, in
660809
}
661810
}
662811

812+
offset_ret = _http3_decode_content_encoding(http_head);
813+
if (offset_ret < 0) {
814+
return offset_ret;
815+
}
816+
663817
if (offset >= http_head->buff_size) {
664818
return -3;
665819
}

test/Makefile

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,25 @@ ifeq ($(filter -j,$(MAKEFLAGS)),)
3434
MAKEFLAGS += -j$(shell nproc)
3535
endif
3636

37+
HASH := \#
38+
WITH_ZLIB ?= auto
39+
HAS_ZLIB := $(shell printf '$(HASH)include <zlib.h>\nint main(void) { return zlibVersion()[0] == 0; }' | $(CC) -x c - -lz -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0)
40+
ifeq ($(WITH_ZLIB), yes)
41+
override WITH_ZLIB := 1
42+
endif
43+
ifeq ($(WITH_ZLIB), auto)
44+
ifeq ($(HAS_ZLIB), 1)
45+
ZLIB_CXXFLAGS += -DWITH_ZLIB
46+
ZLIB_LDFLAGS += -lz
47+
endif
48+
else ifeq ($(WITH_ZLIB), 1)
49+
ZLIB_CXXFLAGS += -DWITH_ZLIB
50+
ZLIB_LDFLAGS += -lz
51+
endif
52+
53+
CXXFLAGS += $(ZLIB_CXXFLAGS)
3754
LDFLAGS += -lssl -lcrypto -lpthread -ldl -lgtest -lstdc++ -lm -rdynamic
55+
LDFLAGS += $(ZLIB_LDFLAGS)
3856
LDFLAGS += $(EXTRA_LDFLAGS)
3957

4058
.PHONY: all clean test $(SMARTDNS_TEST_LIB)
@@ -48,7 +66,7 @@ test: $(BIN)
4866
./$(BIN)
4967

5068
$(SMARTDNS_TEST_LIB):
51-
$(MAKE) DEBUG=1 -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a
69+
$(MAKE) DEBUG=1 WITH_ZLIB=$(WITH_ZLIB) -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a
5270

5371
clean:
5472
$(RM) $(OBJS) $(BIN)

test/cases/test-http.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,39 @@ TEST_F(HTTP, http3_0_parse)
170170
http_head_destroy(http_head);
171171
}
172172

173+
#ifdef WITH_ZLIB
174+
TEST_F(HTTP, http3_0_response_parse_gzip_body)
175+
{
176+
const unsigned char gzip_body[] = {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
177+
0x02, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57,
178+
0x28, 0xcf, 0x2f, 0xca, 0x49, 0x01, 0x00, 0x85,
179+
0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00};
180+
unsigned char buffer[1024];
181+
182+
struct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0);
183+
ASSERT_NE(http_head, nullptr);
184+
http_head_set_httpversion(http_head, "HTTP/3");
185+
http_head_set_httpcode(http_head, 200, "OK");
186+
http_head_add_fields(http_head, "content-type", "application/dns-message");
187+
http_head_add_fields(http_head, "content-encoding", "gzip");
188+
http_head_set_data(http_head, gzip_body, sizeof(gzip_body));
189+
http_head_set_head_type(http_head, HTTP_HEAD_RESPONSE);
190+
191+
int buffer_len = http_head_serialize(http_head, buffer, sizeof(buffer));
192+
ASSERT_GT(buffer_len, 0);
193+
http_head_destroy(http_head);
194+
195+
http_head = http_head_init(1024, HTTP_VERSION_3_0);
196+
ASSERT_NE(http_head, nullptr);
197+
int ret = http_head_parse(http_head, buffer, buffer_len);
198+
ASSERT_EQ(ret, buffer_len);
199+
EXPECT_EQ(http_head_get_httpcode(http_head), 200);
200+
ASSERT_EQ(http_head_get_data_len(http_head), (int)strlen("hello world"));
201+
EXPECT_EQ(memcmp(http_head_get_data(http_head), "hello world", strlen("hello world")), 0);
202+
http_head_destroy(http_head);
203+
}
204+
#endif
205+
173206
TEST_F(HTTP, http1_1_small_buffer)
174207
{
175208
const char *data = "HTTP/1.1 200 OK\r\n"

0 commit comments

Comments
 (0)