quic: encryption-handling config parameters

Ticket: 8116
This commit is contained in:
Philippe Antoine
2026-01-14 12:49:10 +00:00
committed by Victor Julien
parent 844f6011b1
commit 7422c97bf2
4 changed files with 57 additions and 10 deletions
@@ -76,6 +76,7 @@ Encrypted Traffic
The TLS and SSH app layer parsers have the ability to stop processing
encrypted traffic after the initial handshake. By setting the
`app-layer.protocols.tls.encryption-handling` and
`app-layer.protocols.quic.encryption-handling` and
`app-layer.protocols.ssh.encryption-handling` options to `bypass` Suricata
bypasses flows once the handshake is completed and encrypted traffic is
detected. The rest of the flow is ignored.
+6
View File
@@ -37,6 +37,12 @@ dedicated new configuration.
Upgrading to 9.0.0
------------------
Major Changes
~~~~~~~~~~~~~
- Encrypted quic traffic bypass is now independently controlled through
``app-layer.protocols.quic.encryption-handling`` setting. The setting can either
be ``bypass``, ``track-only`` or ``full``.
Logging Changes
~~~~~~~~~~~~~~~
- The format of IKEv1 proposal attributes has been changed to handle
+48 -10
View File
@@ -21,6 +21,8 @@ use super::{
frames::{Frame, QuicTlsExtension, StreamTag},
parser::{quic_pkt_num, QuicData, QuicHeader, QuicType},
};
use crate::conf::conf_get;
use crate::ssh::ssh::SshEncryptionHandling;
use crate::{
applayer::{self, *},
direction::Direction,
@@ -35,12 +37,16 @@ use std::collections::VecDeque;
use std::ffi::CString;
use suricata_sys::sys::{
AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
SCAppLayerParserRegisterLogger, SCAppLayerProtoDetectConfProtoDetectionEnabled,
SCAppLayerParserRegisterLogger, SCAppLayerParserStateSetFlag,
SCAppLayerProtoDetectConfProtoDetectionEnabled,
};
use tls_parser::TlsExtensionType;
static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN;
static mut ENCRYPTION_BYPASS_ENABLED: SshEncryptionHandling =
SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_TRACK_ONLY;
const DEFAULT_DCID_LEN: usize = 16;
const PKT_NUM_BUF_MAX_LEN: usize = 4;
pub(super) const QUIC_MAX_CRYPTO_FRAG_LEN: u64 = 65535;
@@ -251,7 +257,10 @@ impl QuicState {
return Err(());
}
fn handle_frames(&mut self, data: QuicData, header: QuicHeader, to_server: bool) {
fn handle_frames(
&mut self, data: QuicData, header: QuicHeader, to_server: bool,
pstate: *mut AppLayerParserState,
) {
let mut sni: Option<Vec<u8>> = None;
let mut ua: Option<Vec<u8>> = None;
let mut ja3: Option<String> = None;
@@ -311,9 +320,26 @@ impl QuicState {
}
extv.extend_from_slice(&c.extv);
if to_server {
self.hello_ts = true
self.hello_ts = true;
} else {
self.hello_tc = true
self.hello_tc = true;
}
if self.hello_tc && self.hello_ts {
let flags = match unsafe { ENCRYPTION_BYPASS_ENABLED } {
SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_BYPASS => {
APP_LAYER_PARSER_NO_INSPECTION
| APP_LAYER_PARSER_BYPASS_READY
}
SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_TRACK_ONLY => {
APP_LAYER_PARSER_NO_INSPECTION
}
_ => 0,
};
if flags != 0 {
unsafe {
SCAppLayerParserStateSetFlag(pstate, flags);
}
}
}
}
_ => {}
@@ -330,7 +356,7 @@ impl QuicState {
self.transactions.push_back(tx);
}
fn parse(&mut self, input: &[u8], to_server: bool) -> bool {
fn parse(&mut self, input: &[u8], to_server: bool, pstate: *mut AppLayerParserState) -> bool {
// so as to loop over multiple quic headers in one packet
let mut buf = input;
while !buf.is_empty() {
@@ -415,7 +441,7 @@ impl QuicState {
}
match QuicData::from_bytes(framebuf, past_frag, past_fraglen) {
Ok(data) => {
self.handle_frames(data, header, to_server);
self.handle_frames(data, header, to_server, pstate);
}
Err(_e) => {
self.set_event_notx(QuicEvent::ErrorOnData, header, to_server);
@@ -467,13 +493,13 @@ unsafe extern "C" fn quic_probing_parser(
}
unsafe extern "C" fn quic_parse_tc(
_flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
_flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let state = cast_pointer!(state, QuicState);
let buf = stream_slice.as_slice();
if state.parse(buf, false) {
if state.parse(buf, false, pstate) {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
@@ -481,13 +507,13 @@ unsafe extern "C" fn quic_parse_tc(
}
unsafe extern "C" fn quic_parse_ts(
_flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
_flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let state = cast_pointer!(state, QuicState);
let buf = stream_slice.as_slice();
if state.parse(buf, true) {
if state.parse(buf, true, pstate) {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
@@ -587,6 +613,18 @@ pub unsafe extern "C" fn SCRegisterQuicParser() {
ALPROTO_QUIC = alproto;
if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let _ = AppLayerRegisterParser(&parser, alproto);
if let Some(val) = conf_get("app-layer.protocols.quic.encryption-handling") {
let eh = match val {
"full" => SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_FULL,
"track-only" => SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_TRACK_ONLY,
"bypass" => SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_BYPASS,
_ => {
SCFatalErrorOnInit!("Unknown value {} for quic.encryption-handling.", val);
SshEncryptionHandling::SSH_HANDLE_ENCRYPTION_TRACK_ONLY
}
};
unsafe { ENCRYPTION_BYPASS_ENABLED = eh };
}
}
SCLogDebug!("Rust quic parser registered.");
SCAppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_QUIC);
+2
View File
@@ -1249,6 +1249,8 @@ app-layer:
quic:
enabled: yes
# see tls for the different values
#encryption-handling: track-only
dhcp:
enabled: yes