Skip to content

Commit 05554ca

Browse files
authored
Log malformed HTTP/2 requests (#13059)
Unlike HTTP/1 transactions, malformed HTTP/2 requests are rejected before HttpSM creation, so they bypassed the normal transaction logging path. That left malformed h2 traffic out of squid.log even when similar h1 failures were visible. This adds a pre-transaction LogAccess path for malformed h2 request headers and emits a best-effort access log entry before resetting the stream.
1 parent 234bb4a commit 05554ca

22 files changed

Lines changed: 3086 additions & 485 deletions
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/** @file
2+
3+
PreTransactionLogData populates LogData for requests that fail before
4+
HttpSM creation.
5+
6+
@section license License
7+
8+
Licensed to the Apache Software Foundation (ASF) under one
9+
or more contributor license agreements. See the NOTICE file
10+
distributed with this work for additional information
11+
regarding copyright ownership. The ASF licenses this file
12+
to you under the Apache License, Version 2.0 (the
13+
"License"); you may not use this file except in compliance
14+
with the License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS,
20+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
See the License for the specific language governing permissions and
22+
limitations under the License.
23+
*/
24+
25+
#pragma once
26+
27+
#include "proxy/logging/TransactionLogData.h"
28+
29+
#include <string>
30+
31+
/** Populate LogData for requests that never created an HttpSM.
32+
*
33+
* Malformed HTTP/2 or HTTP/3 request headers can be rejected while the
34+
* connection is still decoding and validating the stream, before the request
35+
* progresses far enough to create an HttpSM. This class carries the
36+
* copied request and session metadata needed to emit a best-effort
37+
* transaction log entry for those failures.
38+
*
39+
* Unlike TransactionLogData (which reads from a live HttpSM), this class
40+
* owns its milestones, addresses, and strings because the originating
41+
* stream is about to be destroyed.
42+
*/
43+
class PreTransactionLogData : public TransactionLogData
44+
{
45+
public:
46+
PreTransactionLogData() = default;
47+
48+
~PreTransactionLogData() override
49+
{
50+
if (owned_client_request.valid()) {
51+
owned_client_request.destroy();
52+
}
53+
}
54+
55+
// ===== Milestones =====
56+
57+
TransactionMilestones const *
58+
get_milestones() const override
59+
{
60+
return &owned_milestones;
61+
}
62+
63+
// ===== Headers =====
64+
65+
HTTPHdr *
66+
get_client_request() const override
67+
{
68+
if (owned_client_request.valid()) {
69+
return const_cast<HTTPHdr *>(&owned_client_request);
70+
}
71+
return nullptr;
72+
}
73+
74+
// ===== Client request URL / path =====
75+
76+
const char *
77+
get_client_req_url_str() const override
78+
{
79+
return owned_url.empty() ? nullptr : owned_url.c_str();
80+
}
81+
int
82+
get_client_req_url_len() const override
83+
{
84+
return static_cast<int>(owned_url.size());
85+
}
86+
const char *
87+
get_client_req_url_path_str() const override
88+
{
89+
return owned_path.empty() ? nullptr : owned_path.c_str();
90+
}
91+
int
92+
get_client_req_url_path_len() const override
93+
{
94+
return static_cast<int>(owned_path.size());
95+
}
96+
97+
// ===== Client addressing =====
98+
99+
sockaddr const *
100+
get_client_addr() const override
101+
{
102+
return &owned_client_addr.sa;
103+
}
104+
sockaddr const *
105+
get_client_src_addr() const override
106+
{
107+
return &owned_client_src_addr.sa;
108+
}
109+
sockaddr const *
110+
get_client_dst_addr() const override
111+
{
112+
return &owned_client_dst_addr.sa;
113+
}
114+
uint16_t
115+
get_client_port() const override
116+
{
117+
return m_client_port;
118+
}
119+
120+
// ===== Squid codes =====
121+
122+
SquidLogCode
123+
get_log_code() const override
124+
{
125+
return m_log_code;
126+
}
127+
SquidHitMissCode
128+
get_hit_miss_code() const override
129+
{
130+
return m_hit_miss_code;
131+
}
132+
SquidHierarchyCode
133+
get_hier_code() const override
134+
{
135+
return m_hier_code;
136+
}
137+
138+
// ===== Transaction identifiers =====
139+
140+
int64_t
141+
get_connection_id() const override
142+
{
143+
return m_connection_id;
144+
}
145+
int
146+
get_transaction_id() const override
147+
{
148+
return m_transaction_id;
149+
}
150+
151+
// ===== Protocol info =====
152+
153+
const char *
154+
get_client_protocol() const override
155+
{
156+
return owned_client_protocol_str.empty() ? nullptr : owned_client_protocol_str.c_str();
157+
}
158+
159+
// ===== Connection flags =====
160+
161+
bool
162+
get_client_connection_is_ssl() const override
163+
{
164+
return m_client_connection_is_ssl;
165+
}
166+
167+
// ===== Server transaction count =====
168+
169+
int64_t
170+
get_server_transact_count() const override
171+
{
172+
return m_server_transact_count;
173+
}
174+
175+
// ===== Fallback fields for pre-transaction logging =====
176+
177+
std::string_view
178+
get_method() const override
179+
{
180+
return owned_method;
181+
}
182+
std::string_view
183+
get_scheme() const override
184+
{
185+
return owned_scheme;
186+
}
187+
std::string_view
188+
get_client_protocol_str() const override
189+
{
190+
return owned_client_protocol_str;
191+
}
192+
193+
// ===== Owned backing storage (public for ProxyTransaction to populate). =====
194+
195+
HTTPHdr owned_client_request;
196+
TransactionMilestones owned_milestones;
197+
IpEndpoint owned_client_addr = {};
198+
IpEndpoint owned_client_src_addr = {};
199+
IpEndpoint owned_client_dst_addr = {};
200+
201+
std::string owned_method;
202+
std::string owned_scheme;
203+
std::string owned_authority;
204+
std::string owned_path;
205+
std::string owned_url;
206+
std::string owned_client_protocol_str;
207+
208+
// ===== Simple fields (public for ProxyTransaction to set). =====
209+
210+
uint16_t m_client_port = 0;
211+
SquidLogCode m_log_code = SquidLogCode::EMPTY;
212+
SquidHitMissCode m_hit_miss_code = SQUID_MISS_NONE;
213+
SquidHierarchyCode m_hier_code = SquidHierarchyCode::NONE;
214+
int64_t m_connection_id = 0;
215+
int m_transaction_id = 0;
216+
bool m_client_connection_is_ssl = false;
217+
int64_t m_server_transact_count = 0;
218+
};

include/proxy/ProxyTransaction.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ class ProxyTransaction : public VConnection
146146

147147
void mark_as_tunnel_endpoint() override;
148148

149+
/** Emit a best-effort access log entry for a request that failed before
150+
* HttpSM creation.
151+
*
152+
* Call this when a malformed request is rejected at the protocol layer
153+
* (e.g. during HTTP/2 or HTTP/3 header decoding) and no HttpSM was
154+
* created. The method populates a PreTransactionLogData from the
155+
* session and the partially decoded request, then invokes Log::access.
156+
*
157+
* @param[in] request The decoded (possibly partial) request header.
158+
* @param[in] protocol_str Protocol string for the log entry (e.g. "http/2").
159+
*/
160+
void log_pre_transaction_access(HTTPHdr const *request, const char *protocol_str);
161+
149162
/// Variables
150163
//
151164
HttpSessionAccept::Options upstream_outbound_options; // overwritable copy of options

0 commit comments

Comments
 (0)