Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/http2/logger.rs
Line
Count
Source
1
/* Copyright (C) 2020 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction};
19
use super::parser;
20
use crate::jsonbuilder::{JsonBuilder, JsonError};
21
use std;
22
use std::collections::{HashMap, HashSet};
23
use std::rc::Rc;
24
25
#[derive(Hash, PartialEq, Eq, Debug)]
26
enum HeaderName {
27
    Method,
28
    Path,
29
    Host,
30
    UserAgent,
31
    Status,
32
    ContentLength,
33
}
34
35
12.0k
fn log_http2_headers<'a>(
36
12.0k
    blocks: &'a [parser::HTTP2FrameHeaderBlock], js: &mut JsonBuilder,
37
12.0k
    common: &mut HashMap<HeaderName, &'a Vec<u8>>,
38
12.0k
) -> Result<(), JsonError> {
39
12.0k
    let mut logged_headers = HashSet::new();
40
6.86M
    for block in blocks {
41
        // delay js.start_object() because we skip suplicate headers
42
6.85M
        match block.error {
43
            parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess => {
44
1.62M
                if Rc::strong_count(&block.name) > 2 {
45
                    // more than one reference in headers table + current headers
46
62.7k
                    let ptr = Rc::as_ptr(&block.name) as usize;
47
62.7k
                    if !logged_headers.insert(ptr) {
48
                        // only log once
49
21.2k
                        continue;
50
41.5k
                    }
51
1.56M
                }
52
1.60M
                js.start_object()?;
53
1.60M
                js.set_string_from_bytes("name", &block.name)?;
54
1.60M
                js.set_string_from_bytes("value", &block.value)?;
55
1.60M
                if let Ok(name) = std::str::from_utf8(&block.name) {
56
1.57M
                    match name.to_lowercase().as_ref() {
57
1.57M
                        ":method" => {
58
54.1k
                            common.insert(HeaderName::Method, &block.value);
59
54.1k
                        }
60
1.51M
                        ":path" => {
61
642k
                            common.insert(HeaderName::Path, &block.value);
62
642k
                        }
63
874k
                        ":status" => {
64
62.0k
                            common.insert(HeaderName::Status, &block.value);
65
62.0k
                        }
66
812k
                        "user-agent" => {
67
5.97k
                            common.insert(HeaderName::UserAgent, &block.value);
68
5.97k
                        }
69
806k
                        "host" => {
70
12.4k
                            common.insert(HeaderName::Host, &block.value);
71
12.4k
                        }
72
794k
                        "content-length" => {
73
14.5k
                            common.insert(HeaderName::ContentLength, &block.value);
74
14.5k
                        }
75
779k
                        _ => {}
76
                    }
77
36.2k
                }
78
            }
79
            parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate => {
80
62.8k
                js.start_object()?;
81
62.8k
                js.set_uint("table_size_update", block.sizeupdate)?;
82
            }
83
            _ => {
84
5.16M
                js.start_object()?;
85
5.16M
                js.set_string("error", &block.error.to_string())?;
86
            }
87
        }
88
6.83M
        js.close()?;
89
    }
90
12.0k
    return Ok(());
91
12.0k
}
92
93
17.8k
fn log_headers<'a>(
94
17.8k
    frames: &'a Vec<HTTP2Frame>, js: &mut JsonBuilder,
95
17.8k
    common: &mut HashMap<HeaderName, &'a Vec<u8>>,
96
17.8k
) -> Result<bool, JsonError> {
97
17.8k
    let mut has_headers = false;
98
48.5k
    for frame in frames {
99
30.7k
        match &frame.data {
100
9.76k
            HTTP2FrameTypeData::HEADERS(hd) => {
101
9.76k
                log_http2_headers(&hd.blocks, js, common)?;
102
9.76k
                has_headers = true;
103
            }
104
2.22k
            HTTP2FrameTypeData::PUSHPROMISE(hd) => {
105
2.22k
                log_http2_headers(&hd.blocks, js, common)?;
106
2.22k
                has_headers = true;
107
            }
108
18
            HTTP2FrameTypeData::CONTINUATION(hd) => {
109
18
                log_http2_headers(&hd.blocks, js, common)?;
110
18
                has_headers = true;
111
            }
112
18.7k
            _ => {}
113
        }
114
    }
115
17.8k
    Ok(has_headers)
116
17.8k
}
117
118
17.8k
fn log_http2_frames(frames: &[HTTP2Frame], js: &mut JsonBuilder) -> Result<bool, JsonError> {
119
17.8k
    let mut has_settings = false;
120
48.5k
    for frame in frames {
121
30.7k
        if let HTTP2FrameTypeData::SETTINGS(set) = &frame.data {
122
141
            if !has_settings {
123
141
                js.open_array("settings")?;
124
141
                has_settings = true;
125
0
            }
126
495
            for e in set {
127
354
                js.start_object()?;
128
354
                js.set_string("settings_id", &format!("SETTINGS{}", &e.id.to_string().to_uppercase()))?;
129
354
                js.set_uint("settings_value", e.value as u64)?;
130
354
                js.close()?;
131
            }
132
30.6k
        }
133
    }
134
17.8k
    if has_settings {
135
141
        js.close()?;
136
17.6k
    }
137
138
17.8k
    let mut has_error_code = false;
139
17.8k
    let mut has_priority = false;
140
17.8k
    let mut has_multiple = false;
141
48.5k
    for frame in frames {
142
30.7k
        match &frame.data {
143
0
            HTTP2FrameTypeData::GOAWAY(goaway) => {
144
0
                if !has_error_code {
145
0
                    let errcode: Option<parser::HTTP2ErrorCode> =
146
0
                        num::FromPrimitive::from_u32(goaway.errorcode);
147
0
                    match errcode {
148
0
                        Some(errstr) => {
149
0
                            js.set_string("error_code", &errstr.to_string().to_uppercase())?;
150
                        }
151
                        None => {
152
                            //use uint32
153
0
                            js.set_string("error_code", &goaway.errorcode.to_string())?;
154
                        }
155
                    }
156
0
                    has_error_code = true;
157
0
                } else if !has_multiple {
158
0
                    js.set_string("has_multiple", "error_code")?;
159
0
                    has_multiple = true;
160
0
                }
161
            }
162
40
            HTTP2FrameTypeData::RSTSTREAM(rst) => {
163
40
                if !has_error_code {
164
40
                    let errcode: Option<parser::HTTP2ErrorCode> =
165
40
                        num::FromPrimitive::from_u32(rst.errorcode);
166
40
                    match errcode {
167
39
                        Some(errstr) => {
168
39
                            js.set_string("error_code", &errstr.to_string())?;
169
                        }
170
                        None => {
171
                            //use uint32
172
1
                            js.set_string("error_code", &rst.errorcode.to_string())?;
173
                        }
174
                    }
175
40
                    has_error_code = true;
176
0
                } else if !has_multiple {
177
0
                    js.set_string("has_multiple", "error_code")?;
178
0
                    has_multiple = true;
179
0
                }
180
            }
181
7
            HTTP2FrameTypeData::PRIORITY(priority) => {
182
7
                if !has_priority {
183
6
                    js.set_uint("priority", priority.weight as u64)?;
184
6
                    has_priority = true;
185
1
                } else if !has_multiple {
186
1
                    js.set_string("has_multiple", "priority")?;
187
1
                    has_multiple = true;
188
0
                }
189
            }
190
9.76k
            HTTP2FrameTypeData::HEADERS(hd) => {
191
9.76k
                if let Some(ref priority) = hd.priority {
192
2.19k
                    if !has_priority {
193
2.19k
                        js.set_uint("priority", priority.weight as u64)?;
194
2.19k
                        has_priority = true;
195
7
                    } else if !has_multiple {
196
3
                        js.set_string("has_multiple", "priority")?;
197
3
                        has_multiple = true;
198
4
                    }
199
7.56k
                }
200
            }
201
20.9k
            _ => {}
202
        }
203
    }
204
17.8k
    return Ok(has_settings || has_error_code || has_priority);
205
17.8k
}
206
207
8.92k
fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result<bool, JsonError> {
208
8.92k
    js.set_string("version", "2")?;
209
210
8.92k
    let mut common: HashMap<HeaderName, &Vec<u8>> = HashMap::new();
211
212
8.92k
    let mut has_headers = false;
213
214
    // Request headers.
215
8.92k
    let mark = js.get_mark();
216
8.92k
    js.open_array("request_headers")?;
217
8.92k
    if log_headers(&tx.frames_ts, js, &mut common)? {
218
2.63k
        js.close()?;
219
2.63k
        has_headers = true;
220
    } else {
221
6.28k
        js.restore_mark(&mark)?;
222
    }
223
224
    // Response headers.
225
8.92k
    let mark = js.get_mark();
226
8.92k
    js.open_array("response_headers")?;
227
8.92k
    if log_headers(&tx.frames_tc, js, &mut common)? {
228
6.66k
        js.close()?;
229
6.66k
        has_headers = true;
230
    } else {
231
2.26k
        js.restore_mark(&mark)?;
232
    }
233
234
32.5k
    for (name, value) in common {
235
23.6k
        match name {
236
            HeaderName::Method => {
237
3.85k
                js.set_string_from_bytes("http_method", value)?;
238
            }
239
            HeaderName::Path => {
240
3.83k
                js.set_string_from_bytes("url", value)?;
241
            }
242
            HeaderName::Host => {
243
1.05k
                js.set_string_from_bytes("hostname", value)?;
244
            }
245
            HeaderName::UserAgent => {
246
2.56k
                js.set_string_from_bytes("http_user_agent", value)?;
247
            }
248
            HeaderName::ContentLength => {
249
5.65k
                if let Ok(value) = std::str::from_utf8(value) {
250
5.36k
                    if let Ok(value) = value.parse::<u64>() {
251
4.65k
                        js.set_uint("length", value)?;
252
707
                    }
253
297
                }
254
            }
255
            HeaderName::Status => {
256
6.66k
                if let Ok(value) = std::str::from_utf8(value) {
257
6.35k
                    if let Ok(value) = value.parse::<u64>() {
258
5.24k
                        js.set_uint("status", value)?;
259
1.11k
                    }
260
310
                }
261
            }
262
        }
263
    }
264
265
    // The rest of http2 logging is placed in an "http2" object.
266
8.92k
    js.open_object("http2")?;
267
268
8.92k
    js.set_uint("stream_id", tx.stream_id as u64)?;
269
8.92k
    js.open_object("request")?;
270
8.92k
    let has_request = log_http2_frames(&tx.frames_ts, js)?;
271
8.92k
    js.close()?;
272
273
8.92k
    js.open_object("response")?;
274
8.92k
    let has_response = log_http2_frames(&tx.frames_tc, js)?;
275
8.92k
    js.close()?;
276
277
    // Close http2.
278
8.92k
    js.close()?;
279
280
8.92k
    return Ok(has_request || has_response || has_headers);
281
8.92k
}
282
283
#[no_mangle]
284
8.92k
pub unsafe extern "C" fn rs_http2_log_json(
285
8.92k
    tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
286
8.92k
) -> bool {
287
8.92k
    let tx = cast_pointer!(tx, HTTP2Transaction);
288
8.92k
    if let Ok(x) = log_http2(tx, js) {
289
8.92k
        return x;
290
0
    }
291
0
    return false;
292
8.92k
}