Skip to content

Commit 15818fb

Browse files
committed
fix: Use vhost name instead of Host header in cache key calculation
1 parent 11853e1 commit 15818fb

File tree

3 files changed

+265
-2
lines changed

3 files changed

+265
-2
lines changed

fw/http.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7575,7 +7575,8 @@ tfw_http_hm_srv_send(TfwServer *srv, char *data, unsigned long len)
75757575
}
75767576

75777577
/**
7578-
* Calculate the key of an HTTP request by hashing URI and Host header values.
7578+
* Calculate the key of an HTTP request by hashing URI and vhost name.
7579+
* If vhost is not yet assigned, fall back to using the Host header.
75797580
*/
75807581
unsigned long
75817582
tfw_http_req_key_calc(TfwHttpReq *req)
@@ -7588,8 +7589,22 @@ tfw_http_req_key_calc(TfwHttpReq *req)
75887589
if (test_bit(TFW_HTTP_B_HMONITOR, req->flags))
75897590
return req->hash;
75907591

7591-
if (!TFW_STR_EMPTY(&req->host))
7592+
/*
7593+
* Use vhost name for cache key instead of Host header.
7594+
* This ensures proper cache operation when HTTP chains
7595+
* redirect requests to different vhosts.
7596+
*/
7597+
if (req->vhost && req->vhost->name.len > 0) {
7598+
/* Create a TfwStr from the BasicStr vhost name */
7599+
TfwStr vhost_str = {
7600+
.data = (char *)req->vhost->name.data,
7601+
.len = req->vhost->name.len
7602+
};
7603+
req->hash ^= tfw_hash_str(&vhost_str);
7604+
} else if (!TFW_STR_EMPTY(&req->host)) {
7605+
/* Fallback to Host header if vhost not assigned yet */
75927606
req->hash ^= tfw_hash_str(&req->host);
7607+
}
75937608

75947609
return req->hash;
75957610
}

fw/t/unit/test.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ TEST_SUITE(cfg);
9090
TEST_SUITE(tfw_str);
9191
TEST_SUITE(mem_fast);
9292
TEST_SUITE(http2_parser_hpack);
93+
TEST_SUITE(http_cache_key);
9394
TEST_SUITE(http_sticky);
9495
TEST_SUITE(http_match);
9596
TEST_SUITE(http_msg);
@@ -141,6 +142,9 @@ test_run_all(void)
141142
TEST_SUITE_RUN(http2_parser_hpack);
142143
__fpu_schedule();
143144

145+
TEST_SUITE_RUN(http_cache_key);
146+
__fpu_schedule();
147+
144148
TEST_SUITE_RUN(http_match);
145149
__fpu_schedule();
146150

fw/t/unit/test_http_cache_key.c

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* Tempesta FW
3+
*
4+
* Test for proper cache key calculation when using vhosts and HTTP chains.
5+
*
6+
* Copyright (C) 2023-2025 Tempesta Technologies, Inc.
7+
*
8+
* This program is free software; you can redistribute it and/or modify it
9+
* under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation; either version 2 of the License,
11+
* or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful, but WITHOUT
14+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15+
* FITNESS FOR A PARTICULAR PURPOSE.
16+
* See the GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License along with
19+
* this program; if not, write to the Free Software Foundation, Inc., 59
20+
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21+
*/
22+
23+
#include "helpers.h"
24+
#include "http.h"
25+
#include "test.h"
26+
#include "vhost.h"
27+
28+
/**
29+
* Test that cache key calculation correctly uses vhost name instead of host header.
30+
*/
31+
TEST(http_cache_key, uses_vhost_not_host)
32+
{
33+
TfwHttpReq *req;
34+
unsigned long key1, key2;
35+
TfwVhost vhost1, vhost2;
36+
BasicStr vhost1_name = { .data = "app1.example.com", .len = 15 };
37+
BasicStr vhost2_name = { .data = "app2.example.com", .len = 15 };
38+
39+
/* Create request with Host header */
40+
req = test_req_alloc(1);
41+
EXPECT_NOT_NULL(req);
42+
if (!req)
43+
return;
44+
45+
/* Initialize test vhosts */
46+
memset(&vhost1, 0, sizeof(vhost1));
47+
memset(&vhost2, 0, sizeof(vhost2));
48+
vhost1.name = vhost1_name;
49+
vhost2.name = vhost2_name;
50+
51+
/* Set the Host header to a specific value */
52+
TfwStr host = {
53+
.data = (void *)"same.host.example.com",
54+
.len = 19
55+
};
56+
req->host = host;
57+
58+
/* Set a URI path */
59+
TfwStr uri_path = {
60+
.data = (void *)"/test/path",
61+
.len = 10
62+
};
63+
req->uri_path = uri_path;
64+
65+
/* Calculate cache key with first vhost */
66+
req->vhost = &vhost1;
67+
req->hash = 0; /* Clear cached hash */
68+
key1 = tfw_http_req_key_calc(req);
69+
70+
/* Now change to second vhost, same Host header */
71+
req->vhost = &vhost2;
72+
req->hash = 0; /* Clear cached hash */
73+
key2 = tfw_http_req_key_calc(req);
74+
75+
/* Keys should be different because vhost names are different */
76+
EXPECT_NE(key1, key2);
77+
78+
//test_req_free(req); // TODO: kernel stuck here, why?
79+
}
80+
81+
/**
82+
* Test that the cache key is the same even if the Host header changes
83+
* but the vhost remains the same (which is what happens with HTTP chains)
84+
*/
85+
TEST(http_cache_key, stable_with_http_chains)
86+
{
87+
TfwHttpReq *req;
88+
unsigned long key1, key2;
89+
TfwVhost vhost;
90+
BasicStr vhost_name = { .data = "app2.example.com", .len = 15 };
91+
92+
/* Create request with Host header */
93+
req = test_req_alloc(1);
94+
EXPECT_NOT_NULL(req);
95+
if (!req)
96+
return;
97+
98+
/* Initialize test vhost */
99+
memset(&vhost, 0, sizeof(vhost));
100+
vhost.name = vhost_name;
101+
102+
/* Set a URI path */
103+
TfwStr uri_path = {
104+
.data = (void *)"/test/path",
105+
.len = 10
106+
};
107+
req->uri_path = uri_path;
108+
109+
/* Set the first Host header */
110+
TfwStr host1 = {
111+
.data = (void *)"app1.example.com",
112+
.len = 15
113+
};
114+
req->host = host1;
115+
116+
/* Set vhost to "app2" (as would happen with HTTP chains) */
117+
req->vhost = &vhost;
118+
req->hash = 0; /* Clear cached hash */
119+
key1 = tfw_http_req_key_calc(req);
120+
121+
/* Change Host header but keep same vhost */
122+
TfwStr host2 = {
123+
.data = (void *)"app3.example.com",
124+
.len = 15
125+
};
126+
req->host = host2;
127+
req->hash = 0; /* Clear cached hash */
128+
key2 = tfw_http_req_key_calc(req);
129+
130+
/* Keys should be the same because vhost name is the same */
131+
EXPECT_EQ(key1, key2);
132+
133+
//test_req_free(req); // TODO: kernel stuck here, why?
134+
}
135+
136+
/**
137+
* Test fallback to host header when vhost is NULL
138+
*/
139+
TEST(http_cache_key, fallback_to_host)
140+
{
141+
TfwHttpReq *req;
142+
unsigned long key1, key2;
143+
144+
/* Create request with Host header */
145+
req = test_req_alloc(1);
146+
EXPECT_NOT_NULL(req);
147+
if (!req)
148+
return;
149+
150+
/* Set a URI path */
151+
TfwStr uri_path = {
152+
.data = (void *)"/test/path",
153+
.len = 10
154+
};
155+
req->uri_path = uri_path;
156+
157+
/* Set the first Host header */
158+
TfwStr host1 = {
159+
.data = (void *)"app1.example.com",
160+
.len = 15
161+
};
162+
req->host = host1;
163+
164+
/* No vhost */
165+
req->vhost = NULL;
166+
req->hash = 0; /* Clear cached hash */
167+
key1 = tfw_http_req_key_calc(req);
168+
169+
/* Change Host header, still no vhost */
170+
TfwStr host2 = {
171+
.data = (void *)"app2.example.com",
172+
.len = 15
173+
};
174+
req->host = host2;
175+
req->hash = 0; /* Clear cached hash */
176+
key2 = tfw_http_req_key_calc(req);
177+
178+
/* Keys should be different because host headers are different */
179+
EXPECT_NE(key1, key2);
180+
181+
//test_req_free(req); // TODO: kernel stuck here, why?
182+
}
183+
184+
/**
185+
* Test health monitoring requests are handled correctly
186+
*/
187+
TEST(http_cache_key, health_monitor)
188+
{
189+
TfwHttpReq *req;
190+
unsigned long key1, key2;
191+
TfwVhost vhost;
192+
BasicStr vhost_name = { .data = "app2.example.com", .len = 15 };
193+
194+
/* Create request with Host header */
195+
req = test_req_alloc(1);
196+
EXPECT_NOT_NULL(req);
197+
if (!req)
198+
return;
199+
200+
/* Initialize test vhost */
201+
memset(&vhost, 0, sizeof(vhost));
202+
vhost.name = vhost_name;
203+
204+
/* Set the Host header and URI */
205+
TfwStr host = {
206+
.data = (void *)"app1.example.com",
207+
.len = 15
208+
};
209+
req->host = host;
210+
211+
TfwStr uri_path = {
212+
.data = (void *)"/health",
213+
.len = 7
214+
};
215+
req->uri_path = uri_path;
216+
217+
/* Set health monitor flag */
218+
__set_bit(TFW_HTTP_B_HMONITOR, req->flags);
219+
220+
/* Compute key with vhost */
221+
req->vhost = &vhost;
222+
req->hash = 0;
223+
key1 = tfw_http_req_key_calc(req);
224+
225+
/* Compute key without vhost */
226+
req->vhost = NULL;
227+
req->hash = 0;
228+
key2 = tfw_http_req_key_calc(req);
229+
230+
/* Keys should be the same since only uri_path is used for HM requests */
231+
EXPECT_EQ(key1, key2);
232+
233+
//test_req_free(req); // TODO: kernel stuck here, why?
234+
}
235+
236+
TEST_SUITE(http_cache_key)
237+
{
238+
printk(KERN_INFO "TEST_DEBUG: Starting http_cache_key test suite\n");
239+
TEST_RUN(http_cache_key, uses_vhost_not_host);
240+
TEST_RUN(http_cache_key, stable_with_http_chains);
241+
TEST_RUN(http_cache_key, fallback_to_host);
242+
TEST_RUN(http_cache_key, health_monitor);
243+
printk(KERN_INFO "TEST_DEBUG: http_cache_key test suite completed\n");
244+
}

0 commit comments

Comments
 (0)