]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
quic: Add QUIC App Layer
authorEmmanuel Thompson <eet6646@gmail.com>
Thu, 16 Jul 2020 15:04:52 +0000 (11:04 -0400)
committerVictor Julien <vjulien@oisf.net>
Wed, 19 Jan 2022 13:10:50 +0000 (14:10 +0100)
Parses quic and logs a CYU hash for gquic frames

34 files changed:
rust/Cargo.toml.in
rust/cbindgen.toml
rust/src/lib.rs
rust/src/quic/cyu.rs [new file with mode: 0644]
rust/src/quic/detect.rs [new file with mode: 0644]
rust/src/quic/error.rs [new file with mode: 0644]
rust/src/quic/frames.rs [new file with mode: 0644]
rust/src/quic/logger.rs [new file with mode: 0644]
rust/src/quic/mod.rs [new file with mode: 0644]
rust/src/quic/parser.rs [new file with mode: 0644]
rust/src/quic/quic.rs [new file with mode: 0644]
src/Makefile.am
src/app-layer-detect-proto.c
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/app-layer-quic.c [new file with mode: 0644]
src/app-layer-quic.h [new file with mode: 0644]
src/detect-engine-register.c
src/detect-engine-register.h
src/detect-quic-cyu-hash.c [new file with mode: 0644]
src/detect-quic-cyu-hash.h [new file with mode: 0644]
src/detect-quic-cyu-string.c [new file with mode: 0644]
src/detect-quic-cyu-string.h [new file with mode: 0644]
src/detect-quic-version.c [new file with mode: 0644]
src/detect-quic-version.h [new file with mode: 0644]
src/output-json-alert.c
src/output-json-quic.c [new file with mode: 0644]
src/output-json-quic.h [new file with mode: 0644]
src/output.c
src/suricata-common.h
src/tests/fuzz/confyaml.c
src/util-profiling.c
suricata.yaml.in

index 0987651203a24e8917bbba5b393de6ecaa463802..7dff99e4304e557171c926e57da5924f009a493a 100644 (file)
@@ -58,3 +58,4 @@ suricata-derive = { path = "./derive" }
 
 [dev-dependencies]
 test-case = "~1.1.0"
+hex = "~0.4.3"
index 4f2355fb2fdeda4a4f6830c0358b4d525695e708..ac1b8ed1c2f86d2da949e5a934a397baa4d48136 100644 (file)
@@ -78,6 +78,8 @@ include = [
     "SIPState",
     "ModbusState",
     "CMark",
+    "QuicState",
+    "QuicTransaction"
 ]
 
 # A list of items to not include in the generated bindings
index 5008021f0da6b14b9999a5c44e81838a68ffd993..7c51b76c7d7af1310a662e8a0b71cc3a1e4d901d 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Open Information Security Foundation
+/* Copyright (C) 2017-2021 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -130,6 +130,7 @@ pub mod asn1;
 pub mod mime;
 pub mod ssh;
 pub mod http2;
+pub mod quic;
 pub mod plugin;
 pub mod util;
 pub mod ffi;
diff --git a/rust/src/quic/cyu.rs b/rust/src/quic/cyu.rs
new file mode 100644 (file)
index 0000000..c3352d0
--- /dev/null
@@ -0,0 +1,195 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::{
+    frames::Frame,
+    parser::{QuicHeader, QuicVersion},
+};
+use md5::{Digest, Md5};
+
+#[derive(Debug, PartialEq)]
+pub struct Cyu {
+    pub string: String,
+    pub hash: String,
+}
+
+impl Cyu {
+    pub(crate) fn new(string: String, hash: String) -> Self {
+        Self { string, hash }
+    }
+
+    pub(crate) fn generate(header: &QuicHeader, frames: &[Frame]) -> Vec<Cyu> {
+        let version = match header.version {
+            QuicVersion::Q043 => Some("43"),
+            QuicVersion::Q044 => Some("44"),
+            QuicVersion::Q045 => Some("44"),
+            QuicVersion::Q046 => Some("46"),
+            _ => {
+                SCLogDebug!(
+                    "Cannot match QUIC version {:?} to CYU version",
+                    header.version
+                );
+                None
+            }
+        };
+
+        let mut cyu_hashes = Vec::new();
+
+        if let Some(version) = version {
+            for frame in frames {
+                if let Frame::Stream(stream) = frame {
+                    if let Some(tags) = &stream.tags {
+                        let tags = tags
+                            .iter()
+                            .map(|(tag, _value)| tag.to_string())
+                            .collect::<Vec<String>>()
+                            .join("-");
+
+                        let cyu_string = format!("{},{}", version, tags);
+
+                        let mut hasher = Md5::new();
+                        hasher.update(&cyu_string.as_bytes());
+                        let hash = hasher.finalize();
+
+                        let cyu_hash = format!("{:x}", hash);
+
+                        cyu_hashes.push(Cyu::new(cyu_string, cyu_hash));
+                    }
+                }
+            }
+        }
+
+        cyu_hashes
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::quic::frames::{Frame, Stream, StreamTag};
+    use crate::quic::parser::{PublicFlags, QuicType};
+    use test_case::test_case;
+
+    macro_rules! mock_header_and_frames {
+        ($version:expr, $($variants:expr),+) => {{
+            let header = QuicHeader::new(
+                PublicFlags::new(0x80),
+                QuicType::Initial,
+                $version,
+                vec![],
+                vec![],
+            );
+
+            let frames = vec![
+                Frame::Stream(Stream {
+                    fin: false,
+                    stream_id: vec![],
+                    offset: vec![],
+                    tags: Some(vec![$(($variants, vec![])),*])
+                })
+            ];
+
+            (header, frames)
+        }};
+    }
+
+    // Salesforce tests here:
+    // https://engineering.salesforce.com/gquic-protocol-analysis-and-fingerprinting-in-zeek-a4178855d75f
+    #[test_case(
+        mock_header_and_frames!(
+            // version
+            QuicVersion::Q046,
+            // tags
+            StreamTag::Pad, StreamTag::Sni,
+            StreamTag::Stk, StreamTag::Ver,
+            StreamTag::Ccs, StreamTag::Nonc,
+            StreamTag::Aead, StreamTag::Uaid,
+            StreamTag::Scid, StreamTag::Tcid,
+            StreamTag::Pdmd, StreamTag::Smhl,
+            StreamTag::Icsl, StreamTag::Nonp,
+            StreamTag::Pubs, StreamTag::Mids,
+            StreamTag::Scls, StreamTag::Kexs,
+            StreamTag::Xlct, StreamTag::Csct,
+            StreamTag::Copt, StreamTag::Ccrt,
+            StreamTag::Irtt, StreamTag::Cfcw,
+            StreamTag::Sfcw
+        ),
+        Cyu {
+            string: "46,PAD-SNI-STK-VER-CCS-NONC-AEAD-UAID-SCID-TCID-PDMD-SMHL-ICSL-NONP-PUBS-MIDS-SCLS-KEXS-XLCT-CSCT-COPT-CCRT-IRTT-CFCW-SFCW".to_string(),
+            hash: "a46560d4548108cf99308319b3b85346".to_string(),
+        }; "test cyu 1"
+    )]
+    #[test_case(
+        mock_header_and_frames!(
+            // version
+            QuicVersion::Q043,
+            // tags
+            StreamTag::Pad, StreamTag::Sni,
+            StreamTag::Ver, StreamTag::Ccs,
+            StreamTag::Pdmd, StreamTag::Icsl,
+            StreamTag::Mids, StreamTag::Cfcw,
+            StreamTag::Sfcw
+        ),
+        Cyu {
+            string: "43,PAD-SNI-VER-CCS-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(),
+            hash: "e030dea1f2eea44ac7db5fe4de792acd".to_string(),
+        }; "test cyu 2"
+    )]
+    #[test_case(
+        mock_header_and_frames!(
+            // version
+            QuicVersion::Q043,
+            // tags
+            StreamTag::Pad, StreamTag::Sni,
+            StreamTag::Stk, StreamTag::Ver,
+            StreamTag::Ccs, StreamTag::Scid,
+            StreamTag::Pdmd, StreamTag::Icsl,
+            StreamTag::Mids, StreamTag::Cfcw,
+            StreamTag::Sfcw
+        ),
+        Cyu {
+            string: "43,PAD-SNI-STK-VER-CCS-SCID-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(),
+            hash: "0811fab28e41e8c8a33e220a15b964d9".to_string(),
+        }; "test cyu 3"
+    )]
+    #[test_case(
+        mock_header_and_frames!(
+            // version
+            QuicVersion::Q043,
+            // tags
+            StreamTag::Pad, StreamTag::Sni,
+            StreamTag::Stk, StreamTag::Ver,
+            StreamTag::Ccs, StreamTag::Nonc,
+            StreamTag::Aead, StreamTag::Scid,
+            StreamTag::Pdmd, StreamTag::Icsl,
+            StreamTag::Pubs, StreamTag::Mids,
+            StreamTag::Kexs, StreamTag::Xlct,
+            StreamTag::Cfcw, StreamTag::Sfcw
+        ),
+        Cyu {
+            string: "43,PAD-SNI-STK-VER-CCS-NONC-AEAD-SCID-PDMD-ICSL-PUBS-MIDS-KEXS-XLCT-CFCW-SFCW".to_string(),
+            hash: "d8b208b236d176c89407500dbefb04c2".to_string(),
+        }; "test cyu 4"
+    )]
+    fn test_cyu_generate(input: (QuicHeader, Vec<Frame>), expected: Cyu) {
+        let (header, frames) = input;
+
+        let cyu = Cyu::generate(&header, &frames);
+        assert_eq!(1, cyu.len());
+        assert_eq!(expected, cyu[0]);
+    }
+}
diff --git a/rust/src/quic/detect.rs b/rust/src/quic/detect.rs
new file mode 100644 (file)
index 0000000..c5b1fe1
--- /dev/null
@@ -0,0 +1,65 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use crate::quic::quic::{QuicTransaction};
+use std::ptr;
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_tx_get_cyu_hash(
+    tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+    if (i as usize) < tx.cyu.len() {
+        let cyu = &tx.cyu[i as usize];
+
+        let p = &cyu.hash;
+
+        *buffer = p.as_ptr();
+        *buffer_len = p.len() as u32;
+
+        1
+    } else {
+        *buffer = ptr::null();
+        *buffer_len = 0;
+
+        0
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_tx_get_cyu_string(
+    tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+    if (i as usize) < tx.cyu.len() {
+        let cyu = &tx.cyu[i as usize];
+
+        let p = &cyu.string;
+
+        *buffer = p.as_ptr();
+        *buffer_len = p.len() as u32;
+        1
+    } else {
+        *buffer = ptr::null();
+        *buffer_len = 0;
+
+        0
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_tx_get_version(tx: &QuicTransaction) -> u32 {
+    tx.header.version.into()
+}
diff --git a/rust/src/quic/error.rs b/rust/src/quic/error.rs
new file mode 100644 (file)
index 0000000..4099fe4
--- /dev/null
@@ -0,0 +1,63 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::error::{ErrorKind, ParseError};
+use std::error::Error;
+use std::fmt;
+
+#[derive(Debug, PartialEq)]
+pub enum QuicError {
+    StreamTagNoMatch(u32),
+    InvalidPacket,
+    Incomplete,
+    NomError(ErrorKind),
+}
+
+impl<I> ParseError<I> for QuicError {
+    fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
+        QuicError::NomError(kind)
+    }
+
+    fn append(_input: I, kind: ErrorKind, _other: Self) -> Self {
+        QuicError::NomError(kind)
+    }
+}
+
+impl fmt::Display for QuicError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            QuicError::StreamTagNoMatch(tag) => {
+                write!(f, "Could not match stream tag: 0x{:x}", tag)
+            }
+            QuicError::Incomplete => write!(f, "Incomplete data"),
+            QuicError::InvalidPacket => write!(f, "Invalid packet"),
+            QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e),
+        }
+    }
+}
+
+impl Error for QuicError {}
+
+impl From<nom::Err<QuicError>> for QuicError {
+    fn from(err: nom::Err<QuicError>) -> Self {
+        match err {
+            nom::Err::Incomplete(_) => QuicError::Incomplete,
+            nom::Err::Error(e) => e,
+            nom::Err::Failure(e) => e,
+        }
+    }
+}
diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs
new file mode 100644 (file)
index 0000000..3f880dc
--- /dev/null
@@ -0,0 +1,236 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::error::QuicError;
+use nom::bytes::complete::take;
+use nom::combinator::{all_consuming, complete};
+use nom::multi::{count, many0};
+use nom::number::complete::{be_u16, be_u32, be_u8, le_u16, le_u32};
+use nom::sequence::pair;
+use nom::IResult;
+use num::FromPrimitive;
+use std::fmt;
+
+/// Tuple of StreamTag and offset
+type TagOffset = (StreamTag, u32);
+
+/// Tuple of StreamTag and value
+type TagValue = (StreamTag, Vec<u8>);
+
+#[derive(Debug, PartialEq)]
+pub(crate) struct Stream {
+    pub fin: bool,
+    pub stream_id: Vec<u8>,
+    pub offset: Vec<u8>,
+    pub tags: Option<Vec<TagValue>>,
+}
+
+#[repr(u32)]
+#[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)]
+pub(crate) enum StreamTag {
+    Aead = 0x41454144,
+    Ccrt = 0x43435254,
+    Ccs = 0x43435300,
+    Cetv = 0x43455456,
+    Cfcw = 0x43464357,
+    Chlo = 0x43484c4f,
+    Copt = 0x434f5054,
+    Csct = 0x43534354,
+    Ctim = 0x4354494d,
+    Icsl = 0x4943534c,
+    Irtt = 0x49525454,
+    Kexs = 0x4b455853,
+    Mids = 0x4d494453,
+    Mspc = 0x4d535043,
+    Nonc = 0x4e4f4e43,
+    Nonp = 0x4e4f4e50,
+    Pad = 0x50414400,
+    Pdmd = 0x50444d44,
+    Pubs = 0x50554253,
+    Scid = 0x53434944,
+    Scls = 0x53434c53,
+    Sfcw = 0x53464357,
+    Smhl = 0x534d484c,
+    Sni = 0x534e4900,
+    Sno = 0x534e4f00,
+    Stk = 0x53544b00,
+    Tcid = 0x54434944,
+    Uaid = 0x55414944,
+    Ver = 0x56455200,
+    Xlct = 0x584c4354,
+}
+
+impl fmt::Display for StreamTag {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                StreamTag::Aead => "AEAD",
+                StreamTag::Ccrt => "CCRT",
+                StreamTag::Ccs => "CCS",
+                StreamTag::Cetv => "CETV",
+                StreamTag::Cfcw => "CFCW",
+                StreamTag::Chlo => "CHLO",
+                StreamTag::Copt => "COPT",
+                StreamTag::Csct => "CSCT",
+                StreamTag::Ctim => "CTIM",
+                StreamTag::Icsl => "ICSL",
+                StreamTag::Irtt => "IRTT",
+                StreamTag::Kexs => "KEXS",
+                StreamTag::Mids => "MIDS",
+                StreamTag::Mspc => "MSPC",
+                StreamTag::Nonc => "NONC",
+                StreamTag::Nonp => "NONP",
+                StreamTag::Pad => "PAD",
+                StreamTag::Pdmd => "PDMD",
+                StreamTag::Pubs => "PUBS",
+                StreamTag::Scid => "SCID",
+                StreamTag::Scls => "SCLS",
+                StreamTag::Sfcw => "SFCW",
+                StreamTag::Smhl => "SMHL",
+                StreamTag::Sni => "SNI",
+                StreamTag::Sno => "SNO",
+                StreamTag::Stk => "STK",
+                StreamTag::Tcid => "TCID",
+                StreamTag::Uaid => "UAID",
+                StreamTag::Ver => "VER",
+                StreamTag::Xlct => "XLCT",
+            }
+        )
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum Frame {
+    Padding,
+    Stream(Stream),
+    Unknown(Vec<u8>),
+}
+
+fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> {
+    let (rest, tag) = be_u32(input)?;
+
+    let tag = StreamTag::from_u32(tag).ok_or(nom::Err::Error(QuicError::StreamTagNoMatch(tag)))?;
+
+    Ok((rest, tag))
+}
+
+fn parse_tag_and_offset(input: &[u8]) -> IResult<&[u8], TagOffset, QuicError> {
+    pair(parse_tag, le_u32)(input)
+}
+
+fn parse_crypto_stream(input: &[u8]) -> IResult<&[u8], Vec<TagValue>, QuicError> {
+    // [message tag][number of tag entries: N][pad][[tag][end offset], ...N][value data]
+    let (rest, _message_tag) = parse_tag(input)?;
+
+    let (rest, num_entries) = le_u16(rest)?;
+    let (rest, _padding) = take(2usize)(rest)?;
+
+    let (rest, tags_offset) = count(complete(parse_tag_and_offset), num_entries.into())(rest)?;
+
+    // Convert (Tag, Offset) to (Tag, Value)
+    let mut tags = Vec::new();
+    let mut previous_offset = 0;
+    let mut rest = rest;
+    for (tag, offset) in tags_offset {
+        // offsets should be increasing
+        let value_len = offset
+            .checked_sub(previous_offset)
+            .ok_or(nom::Err::Error(QuicError::InvalidPacket))?;
+        let (new_rest, value) = take(value_len)(rest)?;
+
+        previous_offset = offset;
+        rest = new_rest;
+
+        tags.push((tag, value.to_vec()))
+    }
+
+    Ok((rest, tags))
+}
+
+fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicError> {
+    let rest = input;
+
+    // 0b1_f_d_ooo_ss
+    let fin = frame_ty & 0x40 == 0x40;
+    let has_data_length = frame_ty & 0x20 == 0x20;
+
+    let offset_hdr_length = {
+        let mut offset_length = (frame_ty & 0x1c) >> 2;
+        if offset_length != 0 {
+            offset_length += 1;
+        }
+        offset_length
+    };
+
+    let stream_id_hdr_length = usize::from((frame_ty & 0x03) + 1);
+
+    let (rest, stream_id) = take(stream_id_hdr_length)(rest)?;
+    let (rest, offset) = take(offset_hdr_length)(rest)?;
+
+    let (rest, data_length) = if has_data_length {
+        let (rest, data_length) = be_u16(rest)?;
+
+        (rest, usize::from(data_length))
+    } else {
+        (rest, rest.len())
+    };
+
+    let (rest, stream_data) = take(data_length)(rest)?;
+
+    let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) {
+        Some(tags)
+    } else {
+        None
+    };
+
+    Ok((
+        rest,
+        Frame::Stream(Stream {
+            fin,
+            stream_id: stream_id.to_vec(),
+            offset: offset.to_vec(),
+            tags,
+        }),
+    ))
+}
+
+impl Frame {
+    fn decode_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
+        let (rest, frame_ty) = be_u8(input)?;
+
+        // Special frame types
+        let (rest, value) = if frame_ty & 0x80 == 0x80 {
+            // STREAM
+            parse_stream_frame(rest, frame_ty)?
+        } else {
+            match frame_ty {
+                0x00 => (rest, Frame::Padding),
+                _ => ([].as_ref(), Frame::Unknown(rest.to_vec())),
+            }
+        };
+
+        Ok((rest, value))
+    }
+
+    pub(crate) fn decode_frames(input: &[u8]) -> IResult<&[u8], Vec<Frame>, QuicError> {
+        let (rest, frames) = many0(complete(Frame::decode_frame))(input)?;
+
+        Ok((rest, frames))
+    }
+}
diff --git a/rust/src/quic/logger.rs b/rust/src/quic/logger.rs
new file mode 100644 (file)
index 0000000..619364e
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::quic::QuicTransaction;
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+
+fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
+    js.open_object("quic")?;
+    js.set_uint("version", u32::from(tx.header.version).into())?;
+
+    js.open_array("cyu")?;
+    for cyu in &tx.cyu {
+        js.start_object()?;
+        js.set_string("hash", &cyu.hash)?;
+        js.set_string("string", &cyu.string)?;
+        js.close()?;
+    }
+    js.close()?;
+
+    js.close()?;
+    Ok(())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_to_json(
+    tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
+) -> bool {
+    let tx = cast_pointer!(tx, QuicTransaction);
+    log_template(tx, js).is_ok()
+}
diff --git a/rust/src/quic/mod.rs b/rust/src/quic/mod.rs
new file mode 100644 (file)
index 0000000..5a330e9
--- /dev/null
@@ -0,0 +1,24 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+mod cyu;
+pub mod detect;
+mod error;
+mod frames;
+mod logger;
+mod parser;
+pub mod quic;
diff --git a/rust/src/quic/parser.rs b/rust/src/quic/parser.rs
new file mode 100644 (file)
index 0000000..d3283de
--- /dev/null
@@ -0,0 +1,316 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+use super::error::QuicError;
+use super::frames::Frame;
+use nom::bytes::complete::take;
+use nom::combinator::{all_consuming, map};
+use nom::number::complete::{be_u32, be_u8};
+use nom::IResult;
+
+/*
+   gQUIC is the Google version of QUIC.
+
+   The following docs were referenced when writing this parser
+
+   References:
+       - https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit
+       - https://docs.google.com/document/d/1g5nIXAIkN_Y-7XJW5K45IblHd_L2f5LTaDUDwvZ5L6g/edit
+       - https://www.slideshare.net/shigeki_ohtsu/quic-overview
+       - https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-19.8
+       - https://github.com/salesforce/GQUIC_Protocol_Analyzer/blob/master/src/gquic-protocol.pac
+*/
+
+// List of accepted and tested quic versions format
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct QuicVersion(pub u32);
+
+impl QuicVersion {
+    pub const Q043: QuicVersion = QuicVersion(0x51303433);
+    pub const Q044: QuicVersion = QuicVersion(0x51303434);
+    pub const Q045: QuicVersion = QuicVersion(0x51303435);
+    pub const Q046: QuicVersion = QuicVersion(0x51303436);
+
+    fn is_gquic(&self) -> bool {
+        *self == QuicVersion::Q043
+            || *self == QuicVersion::Q044
+            || *self == QuicVersion::Q045
+            || *self == QuicVersion::Q046
+    }
+}
+
+impl From<QuicVersion> for u32 {
+    fn from(from: QuicVersion) -> Self {
+        from.0
+    }
+}
+
+impl From<u32> for QuicVersion {
+    fn from(from: u32) -> Self {
+        QuicVersion(from)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum QuicType {
+    Initial,
+    Retry,
+    Handshake,
+    ZeroRTT,
+    VersionNegotiation,
+    Short,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PublicFlags {
+    is_long: bool,
+}
+
+impl PublicFlags {
+    pub fn new(value: u8) -> Self {
+        let is_long = value & 0x80 == 0x80;
+
+        PublicFlags { is_long }
+    }
+}
+
+/// A QUIC packet's header.
+#[derive(Debug, PartialEq)]
+pub struct QuicHeader {
+    pub flags: PublicFlags,
+    pub ty: QuicType,
+    pub version: QuicVersion,
+    pub dcid: Vec<u8>,
+    pub scid: Vec<u8>,
+}
+
+#[derive(Debug, PartialEq)]
+pub(crate) struct QuicData {
+    pub frames: Vec<Frame>,
+}
+
+impl QuicHeader {
+    #[cfg(test)]
+    pub(crate) fn new(
+        flags: PublicFlags, ty: QuicType, version: QuicVersion, dcid: Vec<u8>, scid: Vec<u8>,
+    ) -> Self {
+        Self {
+            flags,
+            ty,
+            version,
+            dcid,
+            scid,
+        }
+    }
+
+    pub(crate) fn from_bytes(
+        input: &[u8], dcid_len: usize,
+    ) -> IResult<&[u8], QuicHeader, QuicError> {
+        let (rest, first) = be_u8(input)?;
+        let flags = PublicFlags::new(first);
+
+        if !flags.is_long {
+            // Decode short header
+            let (rest, dcid) = take(dcid_len)(rest)?;
+
+            return Ok((
+                rest,
+                QuicHeader {
+                    flags,
+                    ty: QuicType::Short,
+                    version: QuicVersion(0),
+                    dcid: dcid.to_vec(),
+                    scid: Vec::new(),
+                },
+            ));
+        } else {
+            // Decode Long header
+            let (rest, version) = map(be_u32, QuicVersion)(rest)?;
+
+            let ty = if version == QuicVersion(0) {
+                QuicType::VersionNegotiation
+            } else {
+                // Q046 is when they started using IETF
+                if version.is_gquic() && version != QuicVersion::Q046 {
+                    match first & 0x7f {
+                        0x7f => QuicType::Initial,
+                        0x7e => QuicType::Retry,
+                        0x7d => QuicType::Handshake,
+                        0x7c => QuicType::ZeroRTT,
+                        _ => {
+                            return Err(nom::Err::Error(QuicError::InvalidPacket));
+                        }
+                    }
+                } else {
+                    match (first & 0x30) >> 4 {
+                        0x00 => QuicType::Initial,
+                        0x01 => QuicType::ZeroRTT,
+                        0x02 => QuicType::Handshake,
+                        0x03 => QuicType::Retry,
+                        _ => {
+                            return Err(nom::Err::Error(QuicError::InvalidPacket));
+                        }
+                    }
+                }
+            };
+
+            let (rest, dcid, scid) = if version.is_gquic() {
+                // [DCID_LEN (4)][SCID_LEN (4)]
+                let (rest, lengths) = be_u8(rest)?;
+
+                let mut dcid_len = (lengths & 0xF0) >> 4;
+                let mut scid_len = lengths & 0x0F;
+
+                // Decode dcid length if not 0
+                if dcid_len != 0 {
+                    dcid_len += 3;
+                }
+
+                // Decode scid length if not 0
+                if scid_len != 0 {
+                    scid_len += 3;
+                }
+
+                let (rest, dcid) = take(dcid_len as usize)(rest)?;
+                let (rest, scid) = take(scid_len as usize)(rest)?;
+
+                (rest, dcid.to_vec(), scid.to_vec())
+            } else {
+                let (rest, dcid_len) = be_u8(rest)?;
+                let (rest, dcid) = take(dcid_len as usize)(rest)?;
+
+                let (rest, scid_len) = be_u8(rest)?;
+                let (rest, scid) = take(scid_len as usize)(rest)?;
+                (rest, dcid.to_vec(), scid.to_vec())
+            };
+
+            let rest = match ty {
+                QuicType::Initial => {
+                    let (rest, _pkt_num) = be_u32(rest)?;
+                    let (rest, _msg_auth_hash) = take(12usize)(rest)?;
+
+                    rest
+                }
+                _ => rest,
+            };
+
+            Ok((
+                rest,
+                QuicHeader {
+                    flags,
+                    ty,
+                    version,
+                    dcid,
+                    scid,
+                },
+            ))
+        }
+    }
+}
+
+impl QuicData {
+    pub(crate) fn from_bytes(input: &[u8]) -> Result<QuicData, QuicError> {
+        let (_, frames) = all_consuming(Frame::decode_frames)(input)?;
+        Ok(QuicData { frames })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::quic::frames::{Frame, Stream, StreamTag};
+
+    #[test]
+    fn public_flags_test() {
+        let pf = PublicFlags::new(0xcb);
+        assert_eq!(PublicFlags { is_long: true }, pf);
+    }
+
+    const TEST_DEFAULT_CID_LENGTH: usize = 8;
+
+    #[test]
+    fn test_parse_gquic_unknown_version() {
+        // Test that we can parse unknown versions
+        let data = hex::decode("cbff00001d1091d0b10ac886039973885dfa07c469431409b15e86dd0990aaf906c5de620c4538398ffa58004482d2b19e732fc58818e57cb63569ce11abc00ea4fbac0725c5690a0838c27faf88663b48eca63baf0fba52af4eff7b4117384457c5cf1c1c0e52a1843c7676a4a35cf60c4c179e7186274c121110ace964771f31090f586b283bddbf82e9dd1d6a0e41fbaf243540dfb64f4543e1e87857c77cfc1ee9f883b97b89b6321ce30436119acfdbf2b31f4d0dbac0e5ea740ee59c8619d7c431320504c67f5c3aa9be5192f28ae378e0c8305fb95f01e7cb47c27f92cad7e8d55f699a41df3afe3894939f79e5f164771a6fe987602d975a06bfe8e6906b23601d08bcf2026eac25eca958a7b19ed7ba415e4d31b474264a479c53f01e1d35745ae62a9b148e39e2d7d33176f384d6ce4beb25d2177a8e0fbe5503ea034c9a645e5a8c98098bc5db4e11a351ac72b7079db1a858e11a6c6a4a1f44e1073903029cc08e82c48e6de00f5da7a546db371a4e49d4a213339ca074832cfeb4c39731f98a1683d7fb7db8a5b48c763440d6003fdfadd6a7fb23a62074064aafd585f6a887d5648ce71099d7d21e5cc1e14645f066016a7570d885bde4f239226884ee64fb8ec1218efec83d46ca104d9104bf46637ba3a3d8d6a88967859d60f46198e3a8495f2f211d717c6ca39987d2f4f971b502809932d973736dac67e5e28152c23d844d99fe7a5def822ca97aa433603423ee7fef57e6daa4579bb8f4f14a93663c54db415da5e8b9000d673d99c065c5922ba193eada475f2366b422d42dd86dd3b86fdef67d0e71cd200e3e24b77578f90e0e60717e3a1d6078b836d262028fc73efe7b3684b635f3001225acfd249fbe950dae7c539f015a0ce51c983c4d8e01d7e73e16946e681b2148d0ae4e72fb44d1048eb25572dae0a8016434b8c9e3fd3c93045b8afe67adc6cf7ce61a46819b712a8c24980e6c75bf007adf8910badfa102cd60c96238c8719b5e2b405905cfa6840176c7f71b7d9a2f480c36806f415b93b72821f0547b06f298584be093710a381fa352c34ba221cbcf1bbcd0b7d1aea354e460f6824df14d4bf4377a4503397e70f9993a55905ba298e798d9c69386eae8d0ebf6d871ff75e2d5a546bb8ee6ad9c92d88f950e2d8bc371aaad0d948e9f81c8151c51ee17c9257df4fd27cfeb9944b301a0fff1cb0a1b18836969457edd42f6ba370ecc2e5700bbb9fc15dc9f88c9bfc12c7dda64d423179c1eff8c53cca97056e09a07e29d02b4e553141b78d224cd79ae8056d923d41bc67eec00c943e3a62304487261d0877d54c40b7453c52e6c02141c2fa6601a357d53dddf39ae6e152501813e562a0613ca727ef3b0548c1f5a7e5036a8da84e166cec45de83bf217fb8f6c9a0ea20db0b16d1d2bb9e5e305e9d1f35e3065ab7188f79b9a841d1f6000ea744df1ba49116f7558feedf70677e35985d71b1c87c988d0b1ef2e436a54a77397546513c82bf307fc4b29152cafab11c8527eeda2addd00081c3b7b836a39920322a405c4e3774f20feda9998bf703fd10e93748b7834f3f3794d5b1f3f3099c608e84b025f5675b1526e8feee91ed04f4e91e37bd8e7089ec5a48edc2537bcddbd9d118d7937e2c25fa383186efd2f48fa3f5ebe7eaf544835bb330b61af1a95158c5e").unwrap();
+        let (_rest, value) =
+            QuicHeader::from_bytes(data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap();
+        assert_eq!(
+            QuicHeader {
+                flags: PublicFlags { is_long: true },
+                ty: QuicType::Initial,
+                version: QuicVersion(0xff00001d),
+                dcid: hex::decode("91d0b10ac886039973885dfa07c46943")
+                    .unwrap()
+                    .to_vec(),
+                scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58")
+                    .unwrap()
+                    .to_vec()
+            },
+            value
+        );
+    }
+
+    #[test]
+    fn test_parse_gquic_q044() {
+        let test_data = hex::decode("ff513034345005cad2cc06c4d0e400000001afac230bc5b56fb89800171b800143484c4f09000000504144008f030000534e490098030000564552009c03000043435300ac03000050444d44b00300004943534cb40300004d494453b803000043464357bc03000053464357c003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003132372e302e302e310000000001e8816092921ae87eed8086a215829158353039803a09006400000000c0000000800000").unwrap();
+        let (rest, header) =
+            QuicHeader::from_bytes(test_data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap();
+
+        assert_eq!(
+            QuicHeader {
+                flags: PublicFlags { is_long: true },
+                ty: QuicType::Initial,
+                version: QuicVersion::Q044,
+                dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(),
+                scid: Vec::new(),
+            },
+            header
+        );
+
+        let data = QuicData::from_bytes(rest).unwrap();
+        assert_eq!(
+            QuicData {
+                frames: vec![Frame::Stream(Stream {
+                    fin: false,
+                    stream_id: vec![0x1],
+                    offset: vec![],
+                    tags: Some(vec![
+                        (StreamTag::Pad, [0x0; 911].to_vec()),
+                        (
+                            StreamTag::Sni,
+                            vec![0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31]
+                        ),
+                        (StreamTag::Ver, vec![0x0, 0x0, 0x0, 0x0]),
+                        (
+                            StreamTag::Ccs,
+                            vec![
+                                0x1, 0xe8, 0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80,
+                                0x86, 0xa2, 0x15, 0x82, 0x91
+                            ]
+                        ),
+                        (StreamTag::Pdmd, vec![0x58, 0x35, 0x30, 0x39]),
+                        (StreamTag::Icsl, vec![0x80, 0x3a, 0x9, 0x0]),
+                        (StreamTag::Mids, vec![0x64, 0x0, 0x0, 0x0]),
+                        (StreamTag::Cfcw, vec![0x0, 0xc0, 0x0, 0x0]),
+                        (StreamTag::Sfcw, vec![0x0, 0x80, 0x0, 0x0]),
+                    ])
+                })]
+            },
+            data,
+        );
+    }
+}
diff --git a/rust/src/quic/quic.rs b/rust/src/quic/quic.rs
new file mode 100644 (file)
index 0000000..a7a6cb5
--- /dev/null
@@ -0,0 +1,329 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::{
+    cyu::Cyu,
+    parser::{QuicData, QuicHeader},
+};
+use crate::applayer::{self, *};
+use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP};
+use std::ffi::CString;
+
+static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN;
+
+const DEFAULT_DCID_LEN: usize = 16;
+
+#[derive(Debug)]
+pub struct QuicTransaction {
+    tx_id: u64,
+    pub header: QuicHeader,
+    pub cyu: Vec<Cyu>,
+
+    de_state: Option<*mut core::DetectEngineState>,
+    events: *mut core::AppLayerDecoderEvents,
+    tx_data: AppLayerTxData,
+}
+
+impl QuicTransaction {
+    fn new(header: QuicHeader, data: QuicData) -> Self {
+        let cyu = Cyu::generate(&header, &data.frames);
+        QuicTransaction {
+            tx_id: 0,
+            header,
+            cyu,
+            de_state: None,
+            events: std::ptr::null_mut(),
+            tx_data: AppLayerTxData::new(),
+        }
+    }
+
+    fn free(&mut self) {
+        if !self.events.is_null() {
+            core::sc_app_layer_decoder_events_free_events(&mut self.events);
+        }
+        if let Some(state) = self.de_state {
+            core::sc_detect_engine_state_free(state);
+        }
+    }
+}
+
+impl Drop for QuicTransaction {
+    fn drop(&mut self) {
+        self.free();
+    }
+}
+
+pub struct QuicState {
+    max_tx_id: u64,
+    transactions: Vec<QuicTransaction>,
+}
+
+impl Default for QuicState {
+    fn default() -> Self {
+        Self {
+            max_tx_id: 0,
+            transactions: Vec::new(),
+        }
+    }
+}
+
+impl QuicState {
+    fn new() -> Self {
+        Self::default()
+    }
+
+    // Free a transaction by ID.
+    fn free_tx(&mut self, tx_id: u64) {
+        let tx = self
+            .transactions
+            .iter()
+            .position(|tx| tx.tx_id == tx_id + 1);
+        if let Some(idx) = tx {
+            let _ = self.transactions.remove(idx);
+        }
+    }
+
+    fn get_tx(&mut self, tx_id: u64) -> Option<&QuicTransaction> {
+        self.transactions.iter().find(|&tx| tx.tx_id == tx_id + 1)
+    }
+
+    fn new_tx(&mut self, header: QuicHeader, data: QuicData) -> QuicTransaction {
+        let mut tx = QuicTransaction::new(header, data);
+        self.max_tx_id += 1;
+        tx.tx_id = self.max_tx_id;
+        return tx;
+    }
+
+    fn tx_iterator(
+        &mut self, min_tx_id: u64, state: &mut u64,
+    ) -> Option<(&QuicTransaction, u64, bool)> {
+        let mut index = *state as usize;
+        let len = self.transactions.len();
+
+        while index < len {
+            let tx = &self.transactions[index];
+            if tx.tx_id < min_tx_id + 1 {
+                index += 1;
+                continue;
+            }
+            *state = index as u64;
+            return Some((tx, tx.tx_id - 1, (len - index) > 1));
+        }
+
+        return None;
+    }
+
+    fn parse(&mut self, input: &[u8]) -> bool {
+        match QuicHeader::from_bytes(input, DEFAULT_DCID_LEN) {
+            Ok((rest, header)) => match QuicData::from_bytes(rest) {
+                Ok(data) => {
+                    let transaction = self.new_tx(header, data);
+                    self.transactions.push(transaction);
+
+                    return true;
+                }
+                Err(_e) => {
+                    return false;
+                }
+            },
+            Err(_e) => {
+                return false;
+            }
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_state_new(
+    _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
+) -> *mut std::os::raw::c_void {
+    let state = QuicState::new();
+    let boxed = Box::new(state);
+    return Box::into_raw(boxed) as *mut _;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_state_free(state: *mut std::os::raw::c_void) {
+    // Just unbox...
+    std::mem::drop(unsafe { Box::from_raw(state as *mut QuicState) });
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
+    let state = cast_pointer!(state, QuicState);
+    state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_probing_parser(
+    _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
+) -> AppProto {
+    let slice = build_slice!(input, input_len as usize);
+
+    if QuicHeader::from_bytes(slice, DEFAULT_DCID_LEN).is_ok() {
+        return ALPROTO_QUIC;
+    } else {
+        return ALPROTO_FAILED;
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_parse(
+    _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
+    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) {
+        return AppLayerResult::ok();
+    }
+    return AppLayerResult::err();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_state_get_tx(
+    state: *mut std::os::raw::c_void, tx_id: u64,
+) -> *mut std::os::raw::c_void {
+    let state = cast_pointer!(state, QuicState);
+    match state.get_tx(tx_id) {
+        Some(tx) => {
+            return tx as *const _ as *mut _;
+        }
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
+    let state = cast_pointer!(state, QuicState);
+    return state.max_tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int {
+    // This parser uses 1 to signal transaction completion status.
+    return 1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_tx_get_alstate_progress(
+    tx: *mut std::os::raw::c_void, _direction: u8,
+) -> std::os::raw::c_int {
+    let _tx = cast_pointer!(tx, QuicTransaction);
+    return 1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_state_get_events(
+    tx: *mut std::os::raw::c_void,
+) -> *mut core::AppLayerDecoderEvents {
+    let tx = cast_pointer!(tx, QuicTransaction);
+    return tx.events;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_state_get_event_info(
+    _event_name: *const std::os::raw::c_char, _event_id: *mut std::os::raw::c_int,
+    _event_type: *mut core::AppLayerEventType,
+) -> std::os::raw::c_int {
+    return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_quic_state_get_event_info_by_id(
+    _event_id: std::os::raw::c_int, _event_name: *mut *const std::os::raw::c_char,
+    _event_type: *mut core::AppLayerEventType,
+) -> i8 {
+    return -1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_state_get_tx_iterator(
+    _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64,
+    _max_tx_id: u64, istate: &mut u64,
+) -> applayer::AppLayerGetTxIterTuple {
+    let state = cast_pointer!(state, QuicState);
+    match state.tx_iterator(min_tx_id, istate) {
+        Some((tx, out_tx_id, has_next)) => {
+            let c_tx = tx as *const _ as *mut _;
+            let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next);
+            return ires;
+        }
+        None => {
+            return applayer::AppLayerGetTxIterTuple::not_found();
+        }
+    }
+}
+
+export_tx_data_get!(rs_quic_get_tx_data, QuicTransaction);
+
+// Parser name as a C style string.
+const PARSER_NAME: &[u8] = b"quic\0";
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_quic_register_parser() {
+    let default_port = CString::new("[443,80]").unwrap();
+    let parser = RustParser {
+        name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+        default_port: default_port.as_ptr(),
+        ipproto: IPPROTO_UDP,
+        probe_ts: Some(rs_quic_probing_parser),
+        probe_tc: Some(rs_quic_probing_parser),
+        min_depth: 0,
+        max_depth: 16,
+        state_new: rs_quic_state_new,
+        state_free: rs_quic_state_free,
+        tx_free: rs_quic_state_tx_free,
+        parse_ts: rs_quic_parse,
+        parse_tc: rs_quic_parse,
+        get_tx_count: rs_quic_state_get_tx_count,
+        get_tx: rs_quic_state_get_tx,
+        tx_comp_st_ts: 1,
+        tx_comp_st_tc: 1,
+        tx_get_progress: rs_quic_tx_get_alstate_progress,
+        get_eventinfo: Some(rs_quic_state_get_event_info),
+        get_eventinfo_byid: Some(rs_quic_state_get_event_info_by_id),
+        localstorage_new: None,
+        localstorage_free: None,
+        get_files: None,
+        get_tx_iterator: Some(rs_quic_state_get_tx_iterator),
+        get_tx_data: rs_quic_get_tx_data,
+        apply_tx_config: None,
+        flags: 0,
+        truncate: None,
+        get_frame_id_by_name: None,
+        get_frame_name_by_id: None,
+
+    };
+
+    let ip_proto_str = CString::new("udp").unwrap();
+
+    if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+        let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+        ALPROTO_QUIC = alproto;
+        if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+            let _ = AppLayerRegisterParser(&parser, alproto);
+        }
+        SCLogDebug!("Rust quic parser registered.");
+    } else {
+        SCLogDebug!("Protocol detector and parser disabled for quic.");
+    }
+}
index 475f0892fc32197fb67eb4e0c7f9fbd366088b35..cb4f497374bf73fccca776c760040fb5d12da62b 100755 (executable)
@@ -36,6 +36,7 @@ noinst_HEADERS = \
        app-layer-ike.h \
        app-layer-krb5.h \
        app-layer-modbus.h \
+       app-layer-quic.h \
        app-layer-mqtt.h \
        app-layer-nbss.h \
        app-layer-nfs-tcp.h \
@@ -241,6 +242,9 @@ noinst_HEADERS = \
        detect-mark.h \
        detect-metadata.h \
        detect-modbus.h \
+       detect-quic-version.h \
+       detect-quic-cyu-hash.h \
+       detect-quic-cyu-string.h \
        detect-mqtt-connack-sessionpresent.h \
        detect-mqtt-connect-clientid.h \
        detect-mqtt-connect-flags.h \
@@ -400,6 +404,7 @@ noinst_HEADERS = \
        output-json-krb5.h \
        output-json-metadata.h \
        output-json-modbus.h \
+       output-json-quic.h \
        output-json-mqtt.h \
        output-json-netflow.h \
        output-json-nfs.h \
@@ -627,6 +632,7 @@ libsuricata_c_a_SOURCES = \
        app-layer-ike.c \
        app-layer-krb5.c \
        app-layer-modbus.c \
+       app-layer-quic.c \
        app-layer-mqtt.c \
        app-layer-nfs-tcp.c \
        app-layer-nfs-udp.c \
@@ -826,6 +832,9 @@ libsuricata_c_a_SOURCES = \
        detect-mark.c \
        detect-metadata.c \
        detect-modbus.c \
+       detect-quic-version.c \
+       detect-quic-cyu-hash.c \
+       detect-quic-cyu-string.c \
        detect-mqtt-connack-sessionpresent.c \
        detect-mqtt-connect-clientid.c \
        detect-mqtt-connect-flags.c \
@@ -985,6 +994,7 @@ libsuricata_c_a_SOURCES = \
        output-json-krb5.c \
        output-json-metadata.c \
        output-json-modbus.c \
+       output-json-quic.c \
        output-json-mqtt.c \
        output-json-netflow.c \
        output-json-nfs.c \
index e422297e0f0091370ead44656b121b3e0351e59c..c5a3ba87cde004e15c78ffe32e5e8f346627874e 100644 (file)
@@ -896,6 +896,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                         printf("            alproto: ALPROTO_KRB5\n");
                     else if (pp_pe->alproto == ALPROTO_DHCP)
                         printf("            alproto: ALPROTO_DHCP\n");
+                    else if (pp_pe->alproto == ALPROTO_QUIC)
+                        printf("            alproto: ALPROTO_QUIC\n");
                     else if (pp_pe->alproto == ALPROTO_SNMP)
                         printf("            alproto: ALPROTO_SNMP\n");
                     else if (pp_pe->alproto == ALPROTO_SIP)
@@ -973,6 +975,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                     printf("            alproto: ALPROTO_IKE\n");
                 else if (pp_pe->alproto == ALPROTO_KRB5)
                     printf("            alproto: ALPROTO_KRB5\n");
+                else if (pp_pe->alproto == ALPROTO_QUIC)
+                    printf("            alproto: ALPROTO_QUIC\n");
                 else if (pp_pe->alproto == ALPROTO_DHCP)
                     printf("            alproto: ALPROTO_DHCP\n");
                 else if (pp_pe->alproto == ALPROTO_SNMP)
index a2f643e4457eabd8e2ae9e1b0612d86b4d6a54d6..8ba1826c660dc5d42b1c81affa1184e12d505f8c 100644 (file)
@@ -65,6 +65,7 @@
 #include "app-layer-sip.h"
 #include "app-layer-rfb.h"
 #include "app-layer-mqtt.h"
+#include "app-layer-quic.h"
 #include "app-layer-template.h"
 #include "app-layer-template-rust.h"
 #include "app-layer-rdp.h"
@@ -1657,6 +1658,7 @@ void AppLayerParserRegisterProtocolParsers(void)
     rs_dhcp_register_parser();
     RegisterSNMPParsers();
     RegisterSIPParsers();
+    RegisterQuicParsers();
     RegisterTemplateRustParsers();
     RegisterRFBParsers();
     RegisterMQTTParsers();
index b75a8a56164a61ba61c2570091a47d44ad408beb..11bed306bf17ce373f616d023e39cd0f5cc3d27d 100644 (file)
@@ -93,6 +93,9 @@ const char *AppProtoToString(AppProto alproto)
         case ALPROTO_KRB5:
             proto_name = "krb5";
             break;
+        case ALPROTO_QUIC:
+            proto_name = "quic";
+            break;
         case ALPROTO_DHCP:
             proto_name = "dhcp";
             break;
@@ -170,6 +173,8 @@ AppProto StringToAppProto(const char *proto_name)
     if (strcmp(proto_name, "ike") == 0)
         return ALPROTO_IKE;
     if (strcmp(proto_name,"krb5")==0) return ALPROTO_KRB5;
+    if (strcmp(proto_name, "quic") == 0)
+        return ALPROTO_QUIC;
     if (strcmp(proto_name,"dhcp")==0) return ALPROTO_DHCP;
     if (strcmp(proto_name,"snmp")==0) return ALPROTO_SNMP;
     if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP;
index c31d5c6249d1d24b1c20cbc23752332654388f07..f9a8d213da1c1cf16f13a1bfd8fb61f06952ca10 100644 (file)
@@ -48,6 +48,7 @@ enum AppProtoEnum {
     ALPROTO_TFTP,
     ALPROTO_IKE,
     ALPROTO_KRB5,
+    ALPROTO_QUIC,
     ALPROTO_DHCP,
     ALPROTO_SNMP,
     ALPROTO_SIP,
diff --git a/src/app-layer-quic.c b/src/app-layer-quic.c
new file mode 100644 (file)
index 0000000..837aa9c
--- /dev/null
@@ -0,0 +1,40 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * Quic Application Layer
+ *
+ */
+
+#include "suricata-common.h"
+#include "stream.h"
+#include "conf.h"
+
+#include "util-unittest.h"
+
+#include "app-layer-detect-proto.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-quic.h"
+#include "rust.h"
+
+void RegisterQuicParsers(void)
+{
+    rs_quic_register_parser();
+}
diff --git a/src/app-layer-quic.h b/src/app-layer-quic.h
new file mode 100644 (file)
index 0000000..a0038bc
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ */
+
+#ifndef __APP_LAYER_QUIC_H__
+#define __APP_LAYER_QUIC_H__
+
+void RegisterQuicParsers(void);
+void QuicParserRegisterTests(void);
+
+#endif /* __APP_LAYER_QUIC_H__ */
index c0435667491aaaaf76343d87db143343d20104fc..d3a7c8a425894ad3e3ccc479022e87e1f1d9b9c9 100644 (file)
 #include "detect-mqtt-publish-message.h"
 #include "detect-mqtt-subscribe-topic.h"
 #include "detect-mqtt-unsubscribe-topic.h"
+#include "detect-quic-version.h"
+#include "detect-quic-cyu-hash.h"
+#include "detect-quic-cyu-string.h"
 
 #include "detect-template-buffer.h"
 #include "detect-bypass.h"
@@ -647,6 +650,9 @@ void SigTableSetup(void)
     DetectMQTTPublishMessageRegister();
     DetectMQTTSubscribeTopicRegister();
     DetectMQTTUnsubscribeTopicRegister();
+    DetectQuicVersionRegister();
+    DetectQuicCyuHashRegister();
+    DetectQuicCyuStringRegister();
 
     DetectTemplateBufferRegister();
     DetectBypassRegister();
index a805d4ba29434fdb40559a3e9bc0d75405527196..ac546115163e444fd2082cfd99d4ba28be05a45d 100644 (file)
@@ -286,6 +286,9 @@ enum DetectKeywordId {
     DETECT_AL_MQTT_PUBLISH_MESSAGE,
     DETECT_AL_MQTT_SUBSCRIBE_TOPIC,
     DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC,
+    DETECT_AL_QUIC_VERSION,
+    DETECT_AL_QUIC_CYU_HASH,
+    DETECT_AL_QUIC_CYU_STRING,
     DETECT_AL_TEMPLATE_BUFFER,
 
     DETECT_BYPASS,
diff --git a/src/detect-quic-cyu-hash.c b/src/detect-quic-cyu-hash.c
new file mode 100644 (file)
index 0000000..b2a8a0f
--- /dev/null
@@ -0,0 +1,428 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * Implements the quic.cyu.hash sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-quic-cyu-hash.h"
+#include "rust.h"
+
+#ifdef UNITTESTS
+static void DetectQuicCyuHashRegisterTests(void);
+#endif
+
+#define KEYWORD_NAME "quic.cyu.hash"
+#define KEYWORD_DOC  "quic-cyu.html#quic-cyu-hash"
+#define BUFFER_NAME  "quic.cyu.hash"
+#define BUFFER_DESC  "QUIC CYU Hash"
+static int g_buffer_id = 0;
+
+struct QuicHashGetDataArgs {
+    uint32_t local_id; /**< used as index into thread inspect array */
+    void *txv;
+};
+
+static int DetectQuicCyuHashSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+        return -1;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0)
+        return -1;
+
+    return 0;
+}
+
+static InspectionBuffer *QuicHashGetData(DetectEngineThreadCtx *det_ctx,
+        const DetectEngineTransforms *transforms, Flow *f, struct QuicHashGetDataArgs *cbdata,
+        int list_id, bool first)
+{
+    SCEnter();
+
+    InspectionBuffer *buffer =
+            InspectionBufferMultipleForListGet(det_ctx, list_id, cbdata->local_id);
+    if (buffer == NULL)
+        return NULL;
+    if (!first && buffer->inspect != NULL)
+        return buffer;
+
+    const uint8_t *data;
+    uint32_t data_len;
+    if (rs_quic_tx_get_cyu_hash(cbdata->txv, (uint16_t)cbdata->local_id, &data, &data_len) == 0) {
+        return NULL;
+    }
+
+    InspectionBufferSetupMulti(buffer, transforms, data, data_len);
+
+    SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static int DetectEngineInspectQuicHash(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags,
+        void *alstate, void *txv, uint64_t tx_id)
+{
+    uint32_t local_id = 0;
+
+    const DetectEngineTransforms *transforms = NULL;
+    if (!engine->mpm) {
+        transforms = engine->v2.transforms;
+    }
+
+    while (1) {
+        struct QuicHashGetDataArgs cbdata = {
+            local_id,
+            txv,
+        };
+        InspectionBuffer *buffer =
+                QuicHashGetData(det_ctx, transforms, f, &cbdata, engine->sm_list, false);
+        if (buffer == NULL || buffer->inspect == NULL)
+            break;
+
+        det_ctx->buffer_offset = 0;
+        det_ctx->discontinue_matching = 0;
+        det_ctx->inspection_recursion_counter = 0;
+
+        const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f,
+                (uint8_t *)buffer->inspect, buffer->inspect_len, buffer->inspect_offset,
+                DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+        if (match == 1) {
+            return DETECT_ENGINE_INSPECT_SIG_MATCH;
+        }
+        local_id++;
+    }
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+typedef struct PrefilterMpmQuicHash {
+    int list_id;
+    const MpmCtx *mpm_ctx;
+    const DetectEngineTransforms *transforms;
+} PrefilterMpmQuicHash;
+
+/** \brief QuicHash Mpm prefilter callback
+ *
+ *  \param det_ctx detection engine thread ctx
+ *  \param p packet to inspect
+ *  \param f flow to inspect
+ *  \param txv tx to inspect
+ *  \param pectx inspection context
+ */
+static void PrefilterTxQuicHash(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p,
+        Flow *f, void *txv, const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const PrefilterMpmQuicHash *ctx = (const PrefilterMpmQuicHash *)pectx;
+    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+    const int list_id = ctx->list_id;
+
+    uint32_t local_id = 0;
+    while (1) {
+        // loop until we get a NULL
+
+        struct QuicHashGetDataArgs cbdata = { local_id, txv };
+        InspectionBuffer *buffer =
+                QuicHashGetData(det_ctx, ctx->transforms, f, &cbdata, list_id, true);
+        if (buffer == NULL)
+            break;
+
+        if (buffer->inspect_len >= mpm_ctx->minlen) {
+            (void)mpm_table[mpm_ctx->mpm_type].Search(
+                    mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len);
+        }
+
+        local_id++;
+    }
+}
+
+static void PrefilterMpmQuicHashFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+static int PrefilterMpmQuicHashRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
+        const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+    PrefilterMpmQuicHash *pectx = SCCalloc(1, sizeof(*pectx));
+    if (pectx == NULL)
+        return -1;
+    pectx->list_id = list_id;
+    pectx->mpm_ctx = mpm_ctx;
+    pectx->transforms = &mpm_reg->transforms;
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxQuicHash, mpm_reg->app_v2.alproto,
+            mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmQuicHashFree, mpm_reg->pname);
+}
+
+static bool DetectQuicHashValidateCallback(const Signature *s, const char **sigerror)
+{
+    const SigMatch *sm = s->init_data->smlists[g_buffer_id];
+    for (; sm != NULL; sm = sm->next) {
+        if (sm->type != DETECT_CONTENT)
+            continue;
+
+        const DetectContentData *cd = (DetectContentData *)sm->ctx;
+
+        if (cd->flags & DETECT_CONTENT_NOCASE) {
+            *sigerror = BUFFER_NAME " should not be used together with "
+                                    "nocase, since the rule is automatically "
+                                    "lowercased anyway which makes nocase redundant.";
+            SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror);
+        }
+
+        if (cd->content_len != 32) {
+            *sigerror = "Invalid length of the specified" BUFFER_NAME " (should "
+                        "be 32 characters long). This rule will therefore "
+                        "never match.";
+            SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror);
+            return FALSE;
+        }
+        for (size_t i = 0; i < cd->content_len; ++i) {
+            if (!isxdigit(cd->content[i])) {
+                *sigerror = "Invalid " BUFFER_NAME
+                            " string (should be string of hexadecimal characters)."
+                            "This rule will therefore never match.";
+                SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror);
+                return FALSE;
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+void DetectQuicCyuHashRegister(void)
+{
+    /* quic.cyu.hash sticky buffer */
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].name = KEYWORD_NAME;
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].desc = "sticky buffer to match on the QUIC CYU hash";
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].url = "/rules/" KEYWORD_DOC;
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].Setup = DetectQuicCyuHashSetup;
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].flags |= SIGMATCH_NOOPT;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_AL_QUIC_CYU_HASH].RegisterTests = DetectQuicCyuHashRegisterTests;
+#endif
+
+    DetectAppLayerMpmRegister2(
+            BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterMpmQuicHashRegister, NULL, ALPROTO_QUIC, 1);
+
+    DetectAppLayerInspectEngineRegister2(
+            BUFFER_NAME, ALPROTO_QUIC, SIG_FLAG_TOSERVER, 0, DetectEngineInspectQuicHash, NULL);
+
+    DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+    g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+    DetectBufferTypeRegisterValidateCallback(BUFFER_NAME, DetectQuicHashValidateCallback);
+}
+
+#ifdef UNITTESTS
+#include "app-layer-parser.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "flow-util.h"
+
+/**
+ * \test DetectQuicCyuHashTest01 is a test for a valid quic packet, matching
+ *   on the cyu hash
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int DetectQuicCyuHashTest01(void)
+{
+    /* quic packet */
+    uint8_t buf[] = { 0xc3, 0x51, 0x30, 0x34, 0x36, 0x50, 0x76, 0xd8, 0x63, 0xb7, 0x54, 0xf7, 0xab,
+        0x32, 0x00, 0x00, 0x00, 0x01, 0x54, 0xfd, 0xf4, 0x79, 0x48, 0x76, 0xd0, 0x87, 0x58, 0x8d,
+        0x26, 0x8f, 0xa0, 0x01, 0x04, 0x00, 0x43, 0x48, 0x4c, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x50,
+        0x41, 0x44, 0x00, 0xe4, 0x02, 0x00, 0x00, 0x53, 0x4e, 0x49, 0x00, 0xf7, 0x02, 0x00, 0x00,
+        0x56, 0x45, 0x52, 0x00, 0xfb, 0x02, 0x00, 0x00, 0x43, 0x43, 0x53, 0x00, 0x0b, 0x03, 0x00,
+        0x00, 0x55, 0x41, 0x49, 0x44, 0x2c, 0x03, 0x00, 0x00, 0x54, 0x43, 0x49, 0x44, 0x30, 0x03,
+        0x00, 0x00, 0x50, 0x44, 0x4d, 0x44, 0x34, 0x03, 0x00, 0x00, 0x53, 0x4d, 0x48, 0x4c, 0x38,
+        0x03, 0x00, 0x00, 0x49, 0x43, 0x53, 0x4c, 0x3c, 0x03, 0x00, 0x00, 0x4e, 0x4f, 0x4e, 0x50,
+        0x5c, 0x03, 0x00, 0x00, 0x4d, 0x49, 0x44, 0x53, 0x60, 0x03, 0x00, 0x00, 0x53, 0x43, 0x4c,
+        0x53, 0x64, 0x03, 0x00, 0x00, 0x43, 0x53, 0x43, 0x54, 0x64, 0x03, 0x00, 0x00, 0x43, 0x4f,
+        0x50, 0x54, 0x64, 0x03, 0x00, 0x00, 0x49, 0x52, 0x54, 0x54, 0x68, 0x03, 0x00, 0x00, 0x43,
+        0x46, 0x43, 0x57, 0x6c, 0x03, 0x00, 0x00, 0x53, 0x46, 0x43, 0x57, 0x70, 0x03, 0x00, 0x00,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x31, 0x2e, 0x67,
+        0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x51, 0x30, 0x34, 0x36, 0x01, 0xe8,
+        0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, 0x86, 0xa2, 0x15, 0x82, 0x91, 0x43,
+        0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x37, 0x39, 0x2e, 0x30, 0x2e, 0x33, 0x39, 0x34, 0x35,
+        0x2e, 0x31, 0x31, 0x37, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x78, 0x38, 0x36, 0x5f,
+        0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x58, 0x35, 0x30, 0x39, 0x01, 0x00, 0x00, 0x00, 0x1e,
+        0x00, 0x00, 0x00, 0x82, 0x88, 0x09, 0x00, 0xfa, 0x0f, 0xde, 0xb7, 0x2e, 0x7e, 0x6c, 0x78,
+        0xcc, 0x09, 0x65, 0xab, 0x06, 0x0c, 0x31, 0x05, 0xfa, 0xd9, 0xa2, 0x0b, 0xdd, 0x74, 0x5c,
+        0x28, 0xdf, 0x7b, 0x74, 0x23, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1d, 0x43,
+        0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00 };
+
+    Flow f;
+    void *quic_state = NULL;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    ThreadVars tv;
+    DetectEngineThreadCtx *det_ctx = NULL;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+
+    p = UTHBuildPacketReal(buf, sizeof(buf), IPPROTO_UDP, "192.168.1.5", "192.168.1.1", 41424, 443);
+
+    FLOW_INITIALIZE(&f);
+    f.flags |= FLOW_IPV4;
+    f.proto = IPPROTO_UDP;
+    f.protomap = FlowGetProtoMapping(f.proto);
+
+    p->flow = &f;
+    p->flags |= PKT_HAS_FLOW;
+    p->flowflags |= FLOW_PKT_TOSERVER;
+    f.alproto = ALPROTO_QUIC;
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+    de_ctx->mpm_matcher = mpm_default_matcher;
+    de_ctx->flags |= DE_QUIET;
+
+    s = DetectEngineAppendSig(de_ctx,
+            "alert quic any any -> any any "
+            "(msg:\"Test QUIC CYU hash\"; "
+            "quic.cyu.hash; content:\"910a5e3a4d51593bd59a44611544f209\"; "
+            "sid:1;)");
+    FAIL_IF_NULL(s);
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+    FLOWLOCK_WRLOCK(&f);
+    int r = AppLayerParserParse(
+            NULL, alp_tctx, &f, ALPROTO_QUIC, STREAM_TOSERVER, buf, sizeof(buf));
+    if (r != 0) {
+        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+        FLOWLOCK_UNLOCK(&f);
+        FAIL;
+    }
+    FLOWLOCK_UNLOCK(&f);
+
+    quic_state = f.alstate;
+    FAIL_IF_NULL(quic_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    if (!(PacketAlertCheck(p, 1))) {
+        printf("sig 1 didn't alert, but it should have: ");
+        FAIL;
+    }
+
+    if (alp_tctx != NULL)
+        AppLayerParserThreadCtxFree(alp_tctx);
+    if (det_ctx != NULL)
+        DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    if (de_ctx != NULL)
+        SigGroupCleanup(de_ctx);
+    if (de_ctx != NULL)
+        DetectEngineCtxFree(de_ctx);
+
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+    PASS;
+}
+
+static void DetectQuicCyuHashRegisterTests(void)
+{
+    UtRegisterTest("DetectQuicCyuHashTest01", DetectQuicCyuHashTest01);
+}
+
+#endif /* UNITTESTS */
diff --git a/src/detect-quic-cyu-hash.h b/src/detect-quic-cyu-hash.h
new file mode 100644 (file)
index 0000000..579f1ca
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ */
+
+#ifndef __DETECT_QUIC_CYU_HASH_H__
+#define __DETECT_QUIC_CYU_HASH_H__
+
+void DetectQuicCyuHashRegister(void);
+
+#endif /* __DETECT_QUIC_CYU_HASH_H__ */
diff --git a/src/detect-quic-cyu-string.c b/src/detect-quic-cyu-string.c
new file mode 100644 (file)
index 0000000..5f44f70
--- /dev/null
@@ -0,0 +1,390 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * Implements the quic.cyu.string sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-quic-cyu-string.h"
+#include "rust.h"
+
+#ifdef UNITTESTS
+static void DetectQuicCyuStringRegisterTests(void);
+#endif
+
+#define KEYWORD_NAME "quic.cyu.string"
+#define KEYWORD_DOC  "quic-cyu.html#quic-cyu-string"
+#define BUFFER_NAME  "quic.cyu.string"
+#define BUFFER_DESC  "QUIC CYU String"
+static int g_buffer_id = 0;
+
+struct QuicStringGetDataArgs {
+    uint32_t local_id; /**< used as index into thread inspect array */
+    void *txv;
+};
+
+static int DetectQuicCyuStringSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+        return -1;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0)
+        return -1;
+
+    return 0;
+}
+
+static InspectionBuffer *QuicStringGetData(DetectEngineThreadCtx *det_ctx,
+        const DetectEngineTransforms *transforms, Flow *f, struct QuicStringGetDataArgs *cbdata,
+        int list_id, bool first)
+{
+    SCEnter();
+
+    InspectionBuffer *buffer =
+            InspectionBufferMultipleForListGet(det_ctx, list_id, cbdata->local_id);
+    if (buffer == NULL)
+        return NULL;
+    if (!first && buffer->inspect != NULL)
+        return buffer;
+
+    const uint8_t *data;
+    uint32_t data_len;
+    if (rs_quic_tx_get_cyu_string(cbdata->txv, cbdata->local_id, &data, &data_len) == 0) {
+        return NULL;
+    }
+
+    InspectionBufferSetupMulti(buffer, transforms, data, data_len);
+
+    SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static int DetectEngineInspectQuicString(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags,
+        void *alstate, void *txv, uint64_t tx_id)
+{
+    uint32_t local_id = 0;
+
+    const DetectEngineTransforms *transforms = NULL;
+    if (!engine->mpm) {
+        transforms = engine->v2.transforms;
+    }
+
+    while (1) {
+        struct QuicStringGetDataArgs cbdata = {
+            local_id,
+            txv,
+        };
+        InspectionBuffer *buffer =
+                QuicStringGetData(det_ctx, transforms, f, &cbdata, engine->sm_list, false);
+        if (buffer == NULL || buffer->inspect == NULL)
+            break;
+
+        det_ctx->buffer_offset = 0;
+        det_ctx->discontinue_matching = 0;
+        det_ctx->inspection_recursion_counter = 0;
+
+        const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f,
+                (uint8_t *)buffer->inspect, buffer->inspect_len, buffer->inspect_offset,
+                DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+        if (match == 1) {
+            return DETECT_ENGINE_INSPECT_SIG_MATCH;
+        }
+        local_id++;
+    }
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+/** \brief QuicString Mpm prefilter callback
+ *
+ *  \param det_ctx detection engine thread ctx
+ *  \param p packet to inspect
+ *  \param f flow to inspect
+ *  \param txv tx to inspect
+ *  \param pectx inspection context
+ */
+static void PrefilterTxQuicString(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p,
+        Flow *f, void *txv, const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx;
+    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+    const int list_id = ctx->list_id;
+
+    uint32_t local_id = 0;
+    while (1) {
+        // loop until we get a NULL
+
+        struct QuicStringGetDataArgs cbdata = { local_id, txv };
+        InspectionBuffer *buffer =
+                QuicStringGetData(det_ctx, ctx->transforms, f, &cbdata, list_id, true);
+        if (buffer == NULL)
+            break;
+
+        if (buffer->inspect_len >= mpm_ctx->minlen) {
+            (void)mpm_table[mpm_ctx->mpm_type].Search(
+                    mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len);
+        }
+
+        local_id++;
+    }
+}
+
+static void PrefilterMpmListIdFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+static int PrefilterMpmListIdRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
+        const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+    PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx));
+    if (pectx == NULL)
+        return -1;
+    pectx->list_id = list_id;
+    pectx->mpm_ctx = mpm_ctx;
+    pectx->transforms = &mpm_reg->transforms;
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxQuicString, mpm_reg->app_v2.alproto,
+            mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmListIdFree, mpm_reg->pname);
+}
+
+void DetectQuicCyuStringRegister(void)
+{
+    /* quic.cyu.string sticky buffer */
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].name = KEYWORD_NAME;
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].desc =
+            "sticky buffer to match on the QUIC CYU string";
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].url = "/rules/" KEYWORD_DOC;
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].Setup = DetectQuicCyuStringSetup;
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].flags |= SIGMATCH_NOOPT;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_AL_QUIC_CYU_STRING].RegisterTests = DetectQuicCyuStringRegisterTests;
+#endif
+
+    DetectAppLayerMpmRegister2(
+            BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterMpmListIdRegister, NULL, ALPROTO_QUIC, 1);
+
+    DetectAppLayerInspectEngineRegister2(
+            BUFFER_NAME, ALPROTO_QUIC, SIG_FLAG_TOSERVER, 0, DetectEngineInspectQuicString, NULL);
+
+    DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+    g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+    SCLogDebug("registering " BUFFER_NAME " rule option");
+}
+
+#ifdef UNITTESTS
+#include "app-layer-parser.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "flow-util.h"
+
+/**
+ * \test DetectQuicCyuStringTest01 is a test for a valid quic packet, matching
+ *   on the cyu string
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int DetectQuicCyuStringTest01(void)
+{
+    /* quic packet */
+    uint8_t buf[] = { 0xc3, 0x51, 0x30, 0x34, 0x36, 0x50, 0x76, 0xd8, 0x63, 0xb7, 0x54, 0xf7, 0xab,
+        0x32, 0x00, 0x00, 0x00, 0x01, 0x54, 0xfd, 0xf4, 0x79, 0x48, 0x76, 0xd0, 0x87, 0x58, 0x8d,
+        0x26, 0x8f, 0xa0, 0x01, 0x04, 0x00, 0x43, 0x48, 0x4c, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x50,
+        0x41, 0x44, 0x00, 0xe4, 0x02, 0x00, 0x00, 0x53, 0x4e, 0x49, 0x00, 0xf7, 0x02, 0x00, 0x00,
+        0x56, 0x45, 0x52, 0x00, 0xfb, 0x02, 0x00, 0x00, 0x43, 0x43, 0x53, 0x00, 0x0b, 0x03, 0x00,
+        0x00, 0x55, 0x41, 0x49, 0x44, 0x2c, 0x03, 0x00, 0x00, 0x54, 0x43, 0x49, 0x44, 0x30, 0x03,
+        0x00, 0x00, 0x50, 0x44, 0x4d, 0x44, 0x34, 0x03, 0x00, 0x00, 0x53, 0x4d, 0x48, 0x4c, 0x38,
+        0x03, 0x00, 0x00, 0x49, 0x43, 0x53, 0x4c, 0x3c, 0x03, 0x00, 0x00, 0x4e, 0x4f, 0x4e, 0x50,
+        0x5c, 0x03, 0x00, 0x00, 0x4d, 0x49, 0x44, 0x53, 0x60, 0x03, 0x00, 0x00, 0x53, 0x43, 0x4c,
+        0x53, 0x64, 0x03, 0x00, 0x00, 0x43, 0x53, 0x43, 0x54, 0x64, 0x03, 0x00, 0x00, 0x43, 0x4f,
+        0x50, 0x54, 0x64, 0x03, 0x00, 0x00, 0x49, 0x52, 0x54, 0x54, 0x68, 0x03, 0x00, 0x00, 0x43,
+        0x46, 0x43, 0x57, 0x6c, 0x03, 0x00, 0x00, 0x53, 0x46, 0x43, 0x57, 0x70, 0x03, 0x00, 0x00,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+        0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x31, 0x2e, 0x67,
+        0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x51, 0x30, 0x34, 0x36, 0x01, 0xe8,
+        0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, 0x86, 0xa2, 0x15, 0x82, 0x91, 0x43,
+        0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x37, 0x39, 0x2e, 0x30, 0x2e, 0x33, 0x39, 0x34, 0x35,
+        0x2e, 0x31, 0x31, 0x37, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x78, 0x38, 0x36, 0x5f,
+        0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x58, 0x35, 0x30, 0x39, 0x01, 0x00, 0x00, 0x00, 0x1e,
+        0x00, 0x00, 0x00, 0x82, 0x88, 0x09, 0x00, 0xfa, 0x0f, 0xde, 0xb7, 0x2e, 0x7e, 0x6c, 0x78,
+        0xcc, 0x09, 0x65, 0xab, 0x06, 0x0c, 0x31, 0x05, 0xfa, 0xd9, 0xa2, 0x0b, 0xdd, 0x74, 0x5c,
+        0x28, 0xdf, 0x7b, 0x74, 0x23, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1d, 0x43,
+        0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00 };
+
+    Flow f;
+    void *quic_state = NULL;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    ThreadVars tv;
+    DetectEngineThreadCtx *det_ctx = NULL;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+
+    p = UTHBuildPacketReal(buf, sizeof(buf), IPPROTO_UDP, "192.168.1.5", "192.168.1.1", 41424, 443);
+
+    FLOW_INITIALIZE(&f);
+    f.flags |= FLOW_IPV4;
+    f.proto = IPPROTO_UDP;
+    f.protomap = FlowGetProtoMapping(f.proto);
+
+    p->flow = &f;
+    p->flags |= PKT_HAS_FLOW;
+    p->flowflags |= FLOW_PKT_TOSERVER;
+    f.alproto = ALPROTO_QUIC;
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+    de_ctx->mpm_matcher = mpm_default_matcher;
+    de_ctx->flags |= DE_QUIET;
+
+    s = DetectEngineAppendSig(de_ctx, "alert quic any any -> any any "
+                                      "(msg:\"Test QUIC CYU string\"; "
+                                      "quic.cyu.string; "
+                                      "content:\"46,PAD-SNI-VER-CCS-UAID-TCID-PDMD-SMHL-ICSL-NONP-"
+                                      "MIDS-SCLS-CSCT-COPT-IRTT-CFCW-SFCW\"; "
+                                      "sid:1;)");
+    FAIL_IF_NULL(s);
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+    FLOWLOCK_WRLOCK(&f);
+    int r = AppLayerParserParse(
+            NULL, alp_tctx, &f, ALPROTO_QUIC, STREAM_TOSERVER, buf, sizeof(buf));
+    if (r != 0) {
+        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+        FLOWLOCK_UNLOCK(&f);
+        FAIL;
+    }
+    FLOWLOCK_UNLOCK(&f);
+
+    quic_state = f.alstate;
+    FAIL_IF_NULL(quic_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    if (!(PacketAlertCheck(p, 1))) {
+        printf("sig 1 didn't alert, but it should have: ");
+        FAIL;
+    }
+
+    if (alp_tctx != NULL)
+        AppLayerParserThreadCtxFree(alp_tctx);
+    if (det_ctx != NULL)
+        DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    if (de_ctx != NULL)
+        SigGroupCleanup(de_ctx);
+    if (de_ctx != NULL)
+        DetectEngineCtxFree(de_ctx);
+
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+    PASS;
+}
+
+/**
+ * \brief this function registers unit tests for Quic Cyu String
+ */
+static void DetectQuicCyuStringRegisterTests(void)
+{
+    UtRegisterTest("DetectQuicCyuStringTest01", DetectQuicCyuStringTest01);
+}
+
+#endif /* UNITTESTS */
diff --git a/src/detect-quic-cyu-string.h b/src/detect-quic-cyu-string.h
new file mode 100644 (file)
index 0000000..b521d4f
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ */
+
+#ifndef __DETECT_QUIC_CYU_STRING_H__
+#define __DETECT_QUIC_CYU_STRING_H__
+
+void DetectQuicCyuStringRegister(void);
+
+#endif /* __DETECT_QUIC_CYU_STRING_H__ */
diff --git a/src/detect-quic-version.c b/src/detect-quic-version.c
new file mode 100644 (file)
index 0000000..12640da
--- /dev/null
@@ -0,0 +1,260 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * Implements the quic.version
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-engine-uint.h"
+#include "detect-quic-version.h"
+#include "util-byte.h"
+#include "util-unittest.h"
+#include "rust.h"
+
+#ifdef UNITTESTS
+static void DetectQuicVersionRegisterTests(void);
+#endif
+
+static int quic_version_id = 0;
+
+static int DetectQuicVersionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
+        void *state, void *txv, const Signature *s, const SigMatchCtx *ctx);
+static int DetectQuicVersionSetup(DetectEngineCtx *, Signature *, const char *);
+void DetectQuicVersionFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectQuicVersionGeneric(DetectEngineCtx *de_ctx,
+        DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine,
+        const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for quic.version: keyword
+ */
+void DetectQuicVersionRegister(void)
+{
+    sigmatch_table[DETECT_AL_QUIC_VERSION].name = "quic.version";
+    sigmatch_table[DETECT_AL_QUIC_VERSION].desc = "match Quic version";
+    sigmatch_table[DETECT_AL_QUIC_VERSION].url = "/rules/quic-keywords.html#quic-version";
+    sigmatch_table[DETECT_AL_QUIC_VERSION].AppLayerTxMatch = DetectQuicVersionMatch;
+    sigmatch_table[DETECT_AL_QUIC_VERSION].Setup = DetectQuicVersionSetup;
+    sigmatch_table[DETECT_AL_QUIC_VERSION].Free = DetectQuicVersionFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_AL_QUIC_VERSION].RegisterTests = DetectQuicVersionRegisterTests;
+#endif
+
+    DetectAppLayerInspectEngineRegister2("quic.version", ALPROTO_QUIC, SIG_FLAG_TOSERVER, 1,
+            DetectEngineInspectQuicVersionGeneric, NULL);
+
+    quic_version_id = DetectBufferTypeGetByName("quic.version");
+}
+
+static int DetectEngineInspectQuicVersionGeneric(DetectEngineCtx *de_ctx,
+        DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine,
+        const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+    return DetectEngineInspectGenericList(
+            de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match protocol version of an Quic Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f       Pointer to the current flow.
+ * \param flags   Flags.
+ * \param state   App layer state.
+ * \param txv     Pointer to the transaction.
+ * \param s       Pointer to the Signature.
+ * \param ctx     Pointer to the sigmatch that we will cast into DetectQuicVersionData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectQuicVersionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
+        void *state, void *txv, const Signature *s, const SigMatchCtx *ctx)
+{
+    const DetectU32Data *de = (const DetectU32Data *)ctx;
+    uint32_t version;
+
+    version = rs_quic_tx_get_version(txv);
+
+    return DetectU32Match(version, de);
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed sigmatch  into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectQuicVersionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+    SigMatch *sm = NULL;
+    DetectU32Data *de = NULL;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0)
+        return -1;
+
+    de = DetectU32Parse(rawstr);
+    if (de == NULL)
+        return -1;
+
+    sm = SigMatchAlloc();
+    if (sm == NULL)
+        goto error;
+
+    sm->type = DETECT_AL_QUIC_VERSION;
+    sm->ctx = (SigMatchCtx *)de;
+
+    SigMatchAppendSMToList(s, sm, quic_version_id);
+
+    return 0;
+
+error:
+    if (de != NULL)
+        SCFree(de);
+    if (sm != NULL)
+        SCFree(sm);
+    return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectQuicVersionData
+ *
+ * \param de pointer to DetectQuicVersionData
+ */
+void DetectQuicVersionFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+    if (de_ptr != NULL)
+        SCFree(de_ptr);
+}
+
+#ifdef UNITTESTS
+
+/**
+ * \test QuicVersionTestParse01 is a test for a valid value
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int QuicVersionTestParse01(void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:3; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:3; sid:2; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+
+    PASS;
+}
+
+/**
+ * \test QuicVersionTestParse02 is a test for a valid value
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int QuicVersionTestParse02(void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:>3; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:<44; sid:2; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+
+    PASS;
+}
+
+/**
+ * \test QuicVersionTestParse03 is a test for an invalid value
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int QuicVersionTestParse03(void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:; sid:1; rev:1;)");
+    FAIL_IF_NOT_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+
+    PASS;
+}
+
+/**
+ * \test QuicVersionTestParse04 is a test for an invalid value
+ *
+ *  \retval 1 on success
+ *  \retval 0 on failure
+ */
+static int QuicVersionTestParse04(void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(
+            de_ctx, "alert ip any any -> any any (quic.version:<4294967296; sid:1; rev:1;)");
+    FAIL_IF_NOT_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+
+    PASS;
+}
+
+/**
+ * \brief this function registers unit tests for QuicVersion
+ */
+void DetectQuicVersionRegisterTests(void)
+{
+    UtRegisterTest("QuicVersionTestParse01", QuicVersionTestParse01);
+    UtRegisterTest("QuicVersionTestParse02", QuicVersionTestParse02);
+    UtRegisterTest("QuicVersionTestParse03", QuicVersionTestParse03);
+    UtRegisterTest("QuicVersionTestParse04", QuicVersionTestParse04);
+}
+
+#endif /* UNITTESTS */
diff --git a/src/detect-quic-version.h b/src/detect-quic-version.h
new file mode 100644 (file)
index 0000000..c9e3e19
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ */
+
+#ifndef __DETECT_QUIC_VERSION_H__
+#define __DETECT_QUIC_VERSION_H__
+
+void DetectQuicVersionRegister(void);
+
+#endif /* __DETECT_QUIC_VERSION_H__ */
index dac4e3ff27380c3374c50d8cedc3cb1c2a1720a9..a9307921da04f9853ddf715220f38b522aa5d17c 100644 (file)
@@ -75,6 +75,7 @@
 #include "output-json-ike.h"
 #include "output-json-modbus.h"
 #include "output-json-frame.h"
+#include "output-json-quic.h"
 
 #include "util-byte.h"
 #include "util-privs.h"
@@ -541,6 +542,12 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb,
                 jb_restore_mark(jb, &mark);
             }
             break;
+        case ALPROTO_QUIC:
+            jb_get_mark(jb, &mark);
+            if (!JsonQuicAddMetadata(p->flow, tx_id, jb)) {
+                jb_restore_mark(jb, &mark);
+            }
+            break;
         case ALPROTO_SNMP:
             AlertJsonSNMP(p->flow, tx_id, jb);
             break;
diff --git a/src/output-json-quic.c b/src/output-json-quic.c
new file mode 100644 (file)
index 0000000..8f249ff
--- /dev/null
@@ -0,0 +1,165 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * Implements JSON/eve logging for Quic app-layer.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+#include "util-unittest.h"
+#include "util-buffer.h"
+#include "util-debug.h"
+#include "util-byte.h"
+#include "output.h"
+#include "output-json.h"
+#include "app-layer.h"
+#include "app-layer-parser.h"
+#include "output-json-quic.h"
+#include "rust.h"
+
+typedef struct LogQuicFileCtx_ {
+    LogFileCtx *file_ctx;
+    OutputJsonCtx *eve_ctx;
+} LogQuicFileCtx;
+
+typedef struct JsonQuicLogThread_ {
+    LogQuicFileCtx *quiclog_ctx;
+    OutputJsonThreadCtx *ctx;
+} JsonQuicLogThread;
+
+static int JsonQuicLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state,
+        void *tx, uint64_t tx_id)
+{
+    JsonQuicLogThread *thread = thread_data;
+
+    JsonBuilder *js =
+            CreateEveHeader(p, LOG_DIR_PACKET, "quic", NULL, thread->quiclog_ctx->eve_ctx);
+    if (unlikely(js == NULL)) {
+        return TM_ECODE_OK;
+    }
+    if (!rs_quic_to_json(tx, js)) {
+        jb_free(js);
+        return TM_ECODE_FAILED;
+    }
+    OutputJsonBuilderBuffer(js, thread->ctx);
+
+    jb_free(js);
+    return TM_ECODE_OK;
+}
+
+static void OutputQuicLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+    LogQuicFileCtx *quiclog_ctx = (LogQuicFileCtx *)output_ctx->data;
+    SCFree(quiclog_ctx);
+    SCFree(output_ctx);
+}
+
+static OutputInitResult OutputQuicLogInitSub(ConfNode *conf, OutputCtx *parent_ctx)
+{
+    OutputInitResult result = { NULL, false };
+    OutputJsonCtx *ajt = parent_ctx->data;
+
+    LogQuicFileCtx *quiclog_ctx = SCCalloc(1, sizeof(*quiclog_ctx));
+    if (unlikely(quiclog_ctx == NULL)) {
+        return result;
+    }
+    quiclog_ctx->file_ctx = ajt->file_ctx;
+    quiclog_ctx->eve_ctx = ajt;
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
+    if (unlikely(output_ctx == NULL)) {
+        SCFree(quiclog_ctx);
+        return result;
+    }
+    output_ctx->data = quiclog_ctx;
+    output_ctx->DeInit = OutputQuicLogDeInitCtxSub;
+
+    AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_QUIC);
+
+    result.ctx = output_ctx;
+    result.ok = true;
+    return result;
+}
+
+static TmEcode JsonQuicLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+    if (initdata == NULL) {
+        SCLogDebug("Error getting context for EveLogQuic. \"initdata\" is NULL.");
+        return TM_ECODE_FAILED;
+    }
+
+    JsonQuicLogThread *thread = SCCalloc(1, sizeof(*thread));
+    if (unlikely(thread == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    thread->quiclog_ctx = ((OutputCtx *)initdata)->data;
+    thread->ctx = CreateEveThreadCtx(t, thread->quiclog_ctx->eve_ctx);
+    if (thread->ctx == NULL) {
+        goto error_exit;
+    }
+
+    *data = (void *)thread;
+    return TM_ECODE_OK;
+
+error_exit:
+    SCFree(thread);
+    return TM_ECODE_FAILED;
+}
+
+static TmEcode JsonQuicLogThreadDeinit(ThreadVars *t, void *data)
+{
+    JsonQuicLogThread *thread = (JsonQuicLogThread *)data;
+    if (thread == NULL) {
+        return TM_ECODE_OK;
+    }
+    FreeEveThreadCtx(thread->ctx);
+    SCFree(thread);
+    return TM_ECODE_OK;
+}
+
+bool JsonQuicAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js)
+{
+    void *state = FlowGetAppState(f);
+    if (state) {
+        void *tx = AppLayerParserGetTx(f->proto, ALPROTO_QUIC, state, tx_id);
+        if (tx) {
+            return rs_quic_to_json(tx, js);
+        }
+    }
+
+    return false;
+}
+
+void JsonQuicLogRegister(void)
+{
+    /* Register as an eve sub-module. */
+    OutputRegisterTxSubModule(LOGGER_JSON_QUIC, "eve-log", "JsonQuicLog", "eve-log.quic",
+            OutputQuicLogInitSub, ALPROTO_QUIC, JsonQuicLogger, JsonQuicLogThreadInit,
+            JsonQuicLogThreadDeinit, NULL);
+
+    SCLogDebug("quic json logger registered.");
+}
diff --git a/src/output-json-quic.h b/src/output-json-quic.h
new file mode 100644 (file)
index 0000000..2448d50
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ */
+
+#ifndef __OUTPUT_JSON_QUIC_H__
+#define __OUTPUT_JSON_QUIC_H__
+
+bool JsonQuicAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js);
+void JsonQuicLogRegister(void);
+
+#endif /* __OUTPUT_JSON_QUIC_H__ */
index fcd1eec512db98314ad2d3187f7daba8f9a8fa15..405771513eaf7e85b1f61df4a0e95f07926b9af2 100644 (file)
@@ -71,6 +71,7 @@
 #include "output-json-smb.h"
 #include "output-json-ike.h"
 #include "output-json-krb5.h"
+#include "output-json-quic.h"
 #include "output-json-dhcp.h"
 #include "output-json-snmp.h"
 #include "output-json-sip.h"
@@ -1101,6 +1102,8 @@ void OutputRegisterLoggers(void)
     JsonIKELogRegister();
     /* KRB5 JSON logger. */
     JsonKRB5LogRegister();
+    /* QUIC JSON logger. */
+    JsonQuicLogRegister();
     /* DHCP JSON logger. */
     JsonDHCPLogRegister();
     /* SNMP JSON logger. */
index 84b578d7473b34cbf9037140bbbaef476d5c8743..f6f8bb7621108b7b47be08c3a4ef5c27bce94486 100644 (file)
@@ -454,6 +454,7 @@ typedef enum {
     LOGGER_JSON_SMB,
     LOGGER_JSON_IKE,
     LOGGER_JSON_KRB5,
+    LOGGER_JSON_QUIC,
     LOGGER_JSON_MODBUS,
     LOGGER_JSON_DHCP,
     LOGGER_JSON_SNMP,
index d36131338040397b8ff6df6db62942d5b1d4e5b5..65da8a55187c9ee2561476b9b01b940569d3307f 100644 (file)
@@ -104,4 +104,6 @@ app-layer:\n\
       enabled: yes\n\
     http2:\n\
       enabled: yes\n\
+    quic:\n\
+      enabled: yes\n\
 ";
index 4be72e58d0ef313c9bd8855e44797bd64e8e1927..2a756477fa53d402b078ba99ace43f9ba4fa8c71 100644 (file)
@@ -1306,6 +1306,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id)
         CASE_CODE (LOGGER_JSON_DNP3_TS);
         CASE_CODE (LOGGER_JSON_DNP3_TC);
         CASE_CODE (LOGGER_JSON_HTTP);
+        CASE_CODE(LOGGER_JSON_QUIC);
         CASE_CODE (LOGGER_JSON_DHCP);
         CASE_CODE (LOGGER_JSON_KRB5);
         CASE_CODE(LOGGER_JSON_IKE);
index 1ed10d0b921415fdf5f35997734bde4566fbf493..9e86844ae9161f86ead1a21280925078edf0527e 100644 (file)
@@ -280,6 +280,7 @@ outputs:
         - snmp
         - rfb
         - sip
+        - quic
         - dhcp:
             enabled: yes
             # When extended mode is on, all DHCP messages are logged
@@ -1029,6 +1030,9 @@ app-layer:
     ntp:
       enabled: yes
 
+    quic:
+      enabled: yes
+
     dhcp:
       enabled: yes