mirror of https://github.com/OISF/suricata
quic: track concurrent handshakes per UDP flow
Keep independent Initial keys, connection IDs, CRYPTO reassembly, Hello progress and Retry state for concurrent QUIC connections sharing a UDP five-tuple. Route packets to matching contexts without replacing another unfinished handshake, and validate Retry integrity per connection. Track received CRYPTO byte ranges so duplicates cannot fill gaps and conflicting overlaps are rejected. Bound retained contexts and fragment buffers, expire idle contexts, and report quic.too_many_connections when no completed context can be evicted. Keep application parsing active under track-only handling so later connections can still be inspected. Explicit bypass waits for all known contexts to complete both Hellos and remains a flow-wide opt-out. Add tests for interleaved connections, CID changes and empty IDs, Retry, resource limits, expiry and QUIC v2. Cross-flow migration and Handshake or 1-RTT decryption remain outside the scope of this change. Feature: #9228. https://redmine.openinfosecfoundation.org/issues/9228 Signed-off-by: Shaoqi Jiang <jiangshaoqi9@163.com>pull/16511/head
parent
e16087fab4
commit
75b2c525d0
@ -0,0 +1,149 @@
|
||||
/* 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::{
|
||||
crypto::{decrypt_initial, quic_keys_initial, verify_retry, QuicKeys},
|
||||
frames::Frame,
|
||||
parser::{QuicData, QuicHeader},
|
||||
quic::QuicEvent,
|
||||
reassembly::CryptoReassembly,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
/// A connection is local to its UDP flow; CID migration across flows is not tracked.
|
||||
pub(super) struct QuicConnection {
|
||||
pub version: u32,
|
||||
pub client_scid: Vec<u8>,
|
||||
pub original_dcid: Vec<u8>,
|
||||
initial_dcid: Vec<u8>,
|
||||
server_scid: Option<Vec<u8>>,
|
||||
keys: Option<QuicKeys>,
|
||||
crypto: [CryptoReassembly; 2],
|
||||
hello: [bool; 2],
|
||||
has_retried: bool,
|
||||
pub last_seen: Duration,
|
||||
}
|
||||
|
||||
impl QuicConnection {
|
||||
pub fn new(header: &QuicHeader, now: Duration) -> Self {
|
||||
Self {
|
||||
version: header.version.into(),
|
||||
client_scid: header.scid.clone(),
|
||||
original_dcid: header.dcid.clone(),
|
||||
initial_dcid: header.dcid.clone(),
|
||||
server_scid: None,
|
||||
keys: quic_keys_initial(header.version.into(), &header.dcid),
|
||||
crypto: Default::default(),
|
||||
hello: [false; 2],
|
||||
has_retried: false,
|
||||
last_seen: now,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported(&self, header: &QuicHeader) -> bool {
|
||||
self.keys.is_some() || header.version.is_gquic() || !header.flags.is_long
|
||||
}
|
||||
|
||||
pub fn matches(&self, header: &QuicHeader, to_server: bool) -> bool {
|
||||
if self.version != u32::from(header.version) {
|
||||
return false;
|
||||
}
|
||||
if self.keys.is_none() {
|
||||
// Legacy gQUIC has no Initial key derivation and may omit SCIDs.
|
||||
return self.initial_dcid == header.dcid
|
||||
|| (!to_server && self.initial_dcid == header.scid);
|
||||
}
|
||||
if to_server {
|
||||
self.client_scid == header.scid
|
||||
&& (self.initial_dcid == header.dcid
|
||||
|| self.original_dcid == header.dcid
|
||||
|| self.server_scid.as_deref() == Some(header.dcid.as_slice()))
|
||||
} else {
|
||||
self.client_scid == header.dcid
|
||||
&& self
|
||||
.server_scid
|
||||
.as_deref()
|
||||
.is_none_or(|scid| scid == header.scid)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hello_done(&self, to_server: bool) -> bool {
|
||||
self.hello[usize::from(!to_server)]
|
||||
}
|
||||
|
||||
pub fn complete(&self) -> bool {
|
||||
self.hello[0] && self.hello[1]
|
||||
}
|
||||
|
||||
pub fn decode(
|
||||
&self, header: &QuicHeader, to_server: bool, packet: &[u8], payload: &[u8], hlen: usize,
|
||||
) -> Result<QuicData, QuicEvent> {
|
||||
let mut output = Vec::new();
|
||||
let data = if let Some(keys) = &self.keys {
|
||||
let len = decrypt_initial(keys, to_server, header, payload, packet, hlen, &mut output)
|
||||
.map_err(|_| QuicEvent::FailedDecrypt)?;
|
||||
output.truncate(len);
|
||||
output.as_slice()
|
||||
} else {
|
||||
payload
|
||||
};
|
||||
QuicData::from_bytes(data).map_err(|_| QuicEvent::ErrorOnData)
|
||||
}
|
||||
|
||||
pub fn ingest(
|
||||
&mut self, data: &mut QuicData, header: &QuicHeader, to_server: bool, now: Duration,
|
||||
) -> Result<(), QuicEvent> {
|
||||
let dir = usize::from(!to_server);
|
||||
if !data.frames.iter().any(|f| matches!(f, Frame::Crypto(_))) {
|
||||
for frame in &data.frames {
|
||||
if let Frame::CryptoFrag(frag) = frame {
|
||||
self.crypto[dir].insert(frag.offset, &frag.data)?;
|
||||
}
|
||||
}
|
||||
if let Some(hello) = self.crypto[dir].hello()? {
|
||||
data.frames.push(hello);
|
||||
}
|
||||
}
|
||||
if data.frames.iter().any(|f| matches!(f, Frame::Crypto(_))) {
|
||||
self.hello[dir] = true;
|
||||
self.crypto[dir].clear();
|
||||
}
|
||||
if !to_server && self.keys.is_some() {
|
||||
self.server_scid = Some(header.scid.clone());
|
||||
}
|
||||
self.last_seen = self.last_seen.max(now);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn accepts_retry(&self, header: &QuicHeader, packet: &[u8]) -> bool {
|
||||
self.version == u32::from(header.version)
|
||||
&& header.dcid == self.client_scid
|
||||
&& !self.has_retried
|
||||
&& self.server_scid.is_none()
|
||||
&& header.scid != self.initial_dcid
|
||||
&& verify_retry(self.version, &self.original_dcid, packet)
|
||||
}
|
||||
|
||||
pub fn retry(&mut self, header: &QuicHeader, now: Duration) {
|
||||
self.initial_dcid.clone_from(&header.scid);
|
||||
self.keys = quic_keys_initial(self.version, &self.initial_dcid);
|
||||
self.crypto = Default::default();
|
||||
self.hello = [false; 2];
|
||||
self.has_retried = true;
|
||||
self.last_seen = self.last_seen.max(now);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
/* 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::{parse_quic_handshake, Frame},
|
||||
quic::{QuicEvent, QUIC_MAX_CRYPTO_FRAG_LEN},
|
||||
};
|
||||
use tls_parser::parse_tls_message_handshake;
|
||||
|
||||
/// Initial CRYPTO bytes for one direction of one connection.
|
||||
#[derive(Default)]
|
||||
pub(super) struct CryptoReassembly {
|
||||
data: Vec<u8>,
|
||||
present: Vec<bool>,
|
||||
contiguous: usize,
|
||||
}
|
||||
|
||||
impl CryptoReassembly {
|
||||
pub fn insert(&mut self, offset: u64, data: &[u8]) -> Result<(), QuicEvent> {
|
||||
let end = offset
|
||||
.checked_add(data.len() as u64)
|
||||
.filter(|end| *end < QUIC_MAX_CRYPTO_FRAG_LEN)
|
||||
.ok_or(QuicEvent::CryptoFragTooLong)? as usize;
|
||||
let offset = offset as usize;
|
||||
// Reject conflicting retransmissions before modifying any buffered byte.
|
||||
for (i, byte) in data.iter().enumerate() {
|
||||
let i = offset + i;
|
||||
if i < self.present.len() && self.present[i] && self.data[i] != *byte {
|
||||
return Err(QuicEvent::ErrorOnData);
|
||||
}
|
||||
}
|
||||
if end > self.data.len() {
|
||||
self.data.resize(end, 0);
|
||||
self.present.resize(end, false);
|
||||
}
|
||||
self.data[offset..end].copy_from_slice(data);
|
||||
self.present[offset..end].fill(true);
|
||||
while self.contiguous < self.present.len() && self.present[self.contiguous] {
|
||||
self.contiguous += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hello(&self) -> Result<Option<Frame>, QuicEvent> {
|
||||
if self.contiguous < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
let length = 4
|
||||
+ ((self.data[1] as usize) << 16)
|
||||
+ ((self.data[2] as usize) << 8)
|
||||
+ self.data[3] as usize;
|
||||
if length as u64 >= QUIC_MAX_CRYPTO_FRAG_LEN {
|
||||
return Err(QuicEvent::CryptoFragTooLong);
|
||||
}
|
||||
if self.contiguous < length {
|
||||
return Ok(None);
|
||||
}
|
||||
let (_, msg) = parse_tls_message_handshake(&self.data[..length])
|
||||
.map_err(|_| QuicEvent::ErrorOnData)?;
|
||||
parse_quic_handshake(msg)
|
||||
.map(Some)
|
||||
.ok_or(QuicEvent::ErrorOnData)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn duplicate_fragments_do_not_fill_a_gap() {
|
||||
let mut r = CryptoReassembly::default();
|
||||
r.insert(0, &[1, 0, 0, 8]).unwrap();
|
||||
r.insert(8, &[1, 2, 3, 4]).unwrap();
|
||||
r.insert(8, &[1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(r.contiguous, 4);
|
||||
assert!(r.hello().unwrap().is_none());
|
||||
r.insert(4, &[5, 6, 7, 8]).unwrap();
|
||||
assert_eq!(r.contiguous, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflicting_overlap_is_rejected() {
|
||||
let mut r = CryptoReassembly::default();
|
||||
r.insert(2, &[1, 2, 3]).unwrap();
|
||||
assert!(matches!(r.insert(3, &[9]), Err(QuicEvent::ErrorOnData)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offsets_are_bounded_before_allocating() {
|
||||
let mut r = CryptoReassembly::default();
|
||||
assert!(matches!(
|
||||
r.insert(u64::MAX, &[1]),
|
||||
Err(QuicEvent::CryptoFragTooLong)
|
||||
));
|
||||
assert!(r.data.is_empty());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,376 @@
|
||||
/* 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::QuicState;
|
||||
use crate::encryption::EncryptionHandling;
|
||||
use crate::quic::crypto::test_packets::{initial, retry};
|
||||
use std::{ptr, time::Duration};
|
||||
|
||||
fn hello(name: &str) -> Vec<u8> {
|
||||
let mut body = vec![3, 3];
|
||||
body.extend([0; 32]);
|
||||
body.extend([0, 0, 2, 0x13, 1, 1, 0]); // session, cipher, compression
|
||||
let mut sni = ((name.len() + 3) as u16).to_be_bytes().to_vec();
|
||||
sni.push(0);
|
||||
sni.extend_from_slice(&(name.len() as u16).to_be_bytes());
|
||||
sni.extend_from_slice(name.as_bytes());
|
||||
let mut ext = vec![0, 0];
|
||||
ext.extend_from_slice(&(sni.len() as u16).to_be_bytes());
|
||||
ext.extend(sni);
|
||||
body.extend_from_slice(&(ext.len() as u16).to_be_bytes());
|
||||
body.extend(ext);
|
||||
let mut result = vec![1, 0, 0, body.len() as u8];
|
||||
result.extend(body);
|
||||
result
|
||||
}
|
||||
|
||||
fn server_hello() -> Vec<u8> {
|
||||
let mut b = vec![2, 0, 0, 40, 3, 3];
|
||||
b.extend([0; 32]);
|
||||
b.extend([0, 0x13, 1, 0, 0, 0]);
|
||||
b
|
||||
}
|
||||
|
||||
fn parse(state: &mut QuicState, packet: &[u8], client: bool, time: u64) {
|
||||
assert!(state.parse(packet, client, ptr::null_mut(), Duration::from_secs(time)));
|
||||
}
|
||||
|
||||
fn names(state: &QuicState) -> Vec<&[u8]> {
|
||||
state
|
||||
.transactions
|
||||
.iter()
|
||||
.filter_map(|tx| tx.sni.as_deref())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_interleaved_connections_reassemble_independently() {
|
||||
let mut s = QuicState::new();
|
||||
let messages = [hello("a.example"), hello("b.example"), hello("c.example")];
|
||||
for (i, message) in messages.iter().enumerate() {
|
||||
let d = [i as u8 + 1; 8];
|
||||
let c = [i as u8 + 11; 8];
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &d, &d, &c, 0, true, 0, &message[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
}
|
||||
assert!(names(&s).is_empty());
|
||||
// A repeated tail cannot fill the gap between its first and last fragments.
|
||||
let a_tail = initial(
|
||||
1,
|
||||
&[1; 8],
|
||||
&[1; 8],
|
||||
&[11; 8],
|
||||
1,
|
||||
true,
|
||||
40,
|
||||
&messages[0][40..],
|
||||
);
|
||||
parse(&mut s, &a_tail, true, 2);
|
||||
parse(&mut s, &a_tail, true, 2);
|
||||
assert!(names(&s).is_empty());
|
||||
for (i, message) in messages.iter().enumerate().rev() {
|
||||
let d = [i as u8 + 1; 8];
|
||||
let c = [i as u8 + 11; 8];
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &d, &d, &c, 2, true, 20, &message[20..]),
|
||||
true,
|
||||
3,
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
names(&s),
|
||||
vec![b"c.example".as_slice(), b"b.example", b"a.example"]
|
||||
);
|
||||
assert_eq!(s.connections.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_cid_switch_preserves_initial_keys_and_other_connections() {
|
||||
let mut s = QuicState::new();
|
||||
let a = hello("a.example");
|
||||
let b = hello("b.example");
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[1; 8], &[11; 8], 0, true, 0, &a[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 0, true, 0, &b[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[11; 8], &[21; 8], 0, false, 0, &server_hello()),
|
||||
false,
|
||||
2,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[21; 8], &[11; 8], 1, true, 20, &a[20..]),
|
||||
true,
|
||||
3,
|
||||
);
|
||||
assert!(s.connections[0].complete());
|
||||
assert!(!s.connections[1].complete());
|
||||
assert_eq!(
|
||||
s.encryption_flags(EncryptionHandling::ENCRYPTION_HANDLING_BYPASS),
|
||||
0
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 1, true, 20, &b[20..]),
|
||||
true,
|
||||
4,
|
||||
);
|
||||
assert_eq!(names(&s), vec![b"a.example".as_slice(), b"b.example"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_length_client_ids_use_the_correct_server_initial_keys() {
|
||||
let mut s = QuicState::new();
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[i; 8], &[i; 8], &[], 0, true, 0, &hello("zero.example")),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
}
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[i; 8], &[], &[i + 20; 8], 0, false, 0, &server_hello()),
|
||||
false,
|
||||
2,
|
||||
);
|
||||
}
|
||||
assert_eq!(s.connections.len(), 2);
|
||||
assert!(s.connections.iter().all(|c| c.complete()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_resets_only_its_connection_and_requires_integrity() {
|
||||
let mut s = QuicState::new();
|
||||
let a = hello("retry.example");
|
||||
let b = hello("other.example");
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[1; 8], &[11; 8], 0, true, 0, &a[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 0, true, 0, &b[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
let good = retry(&[11; 8], &[31; 8], &[1; 8]);
|
||||
let mut bad = good.clone();
|
||||
*bad.last_mut().unwrap() ^= 1;
|
||||
parse(&mut s, &bad, false, 2);
|
||||
// Invalid Retry must not discard the original CRYPTO prefix.
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[1; 8], &[11; 8], 1, true, 20, &a[20..]),
|
||||
true,
|
||||
3,
|
||||
);
|
||||
parse(&mut s, &good, false, 4);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[31; 8], &[31; 8], &[11; 8], 2, true, 0, &a),
|
||||
true,
|
||||
5,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 1, true, 20, &b[20..]),
|
||||
true,
|
||||
5,
|
||||
);
|
||||
assert_eq!(s.connections.len(), 2);
|
||||
assert_eq!(
|
||||
names(&s),
|
||||
vec![
|
||||
b"retry.example".as_slice(),
|
||||
b"retry.example",
|
||||
b"other.example"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_initial_and_capacity_limit_preserve_unfinished_connections() {
|
||||
let mut s = QuicState::new();
|
||||
s.max_connections = 2;
|
||||
let h = hello("bounded.example");
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[i; 8], &[i; 8], &[i + 10; 8], 0, true, 0, &h[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
}
|
||||
let mut invalid = initial(1, &[3; 8], &[3; 8], &[13; 8], 0, true, 0, &h);
|
||||
*invalid.last_mut().unwrap() ^= 1;
|
||||
parse(&mut s, &invalid, true, 2);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[3; 8], &[3; 8], &[13; 8], 1, true, 0, &h),
|
||||
true,
|
||||
2,
|
||||
);
|
||||
assert_eq!(s.connections.len(), 2);
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[i; 8], &[i; 8], &[i + 10; 8], 1, true, 20, &h[20..]),
|
||||
true,
|
||||
3,
|
||||
);
|
||||
}
|
||||
assert_eq!(names(&s).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idle_contexts_expire_without_expiring_active_connections() {
|
||||
let mut s = QuicState::new();
|
||||
s.idle_timeout = Duration::from_secs(10);
|
||||
let h = hello("expiry.example");
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[1; 8], &[11; 8], 0, true, 0, &h[..20]),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 0, true, 0, &h[..20]),
|
||||
true,
|
||||
8,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[2; 8], &[2; 8], &[12; 8], 1, true, 20, &h[20..]),
|
||||
true,
|
||||
12,
|
||||
);
|
||||
assert_eq!(s.connections.len(), 1);
|
||||
assert_eq!(s.connections[0].client_scid, [12; 8]);
|
||||
assert_eq!(names(&s), vec![b"expiry.example".as_slice()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quic_v2_connections_are_independent() {
|
||||
let mut s = QuicState::new();
|
||||
let h = hello("v2.example");
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(
|
||||
0x6b3343cf,
|
||||
&[i; 8],
|
||||
&[i; 8],
|
||||
&[i + 10; 8],
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
&h[..20],
|
||||
),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
}
|
||||
for i in 1..=2 {
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(
|
||||
0x6b3343cf,
|
||||
&[i; 8],
|
||||
&[i; 8],
|
||||
&[i + 10; 8],
|
||||
1,
|
||||
true,
|
||||
20,
|
||||
&h[20..],
|
||||
),
|
||||
true,
|
||||
2,
|
||||
);
|
||||
}
|
||||
assert_eq!(names(&s).len(), 2);
|
||||
assert_eq!(s.connections.len(), 2);
|
||||
}
|
||||
|
||||
// Rust-only tests pass a null parser state. End-to-end tests exercise the real C API.
|
||||
#[no_mangle]
|
||||
extern "C" fn SCAppLayerParserStateSetFlag(_: *mut suricata_sys::sys::AppLayerParserState, _: u16) {
|
||||
panic!("Rust tests must not call the C parser-state API");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypted_cid_rotation_after_hello_does_not_create_new_transactions() {
|
||||
let mut s = QuicState::new();
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(
|
||||
1,
|
||||
&[1; 8],
|
||||
&[1; 8],
|
||||
&[11; 8],
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
&hello("done.example"),
|
||||
),
|
||||
true,
|
||||
1,
|
||||
);
|
||||
parse(
|
||||
&mut s,
|
||||
&initial(1, &[1; 8], &[11; 8], &[21; 8], 0, false, 0, &server_hello()),
|
||||
false,
|
||||
2,
|
||||
);
|
||||
let count = s.transactions.len();
|
||||
let mut handshake = vec![0xe0, 0, 0, 0, 1, 8];
|
||||
handshake.extend([99; 8]);
|
||||
handshake.push(8);
|
||||
handshake.extend([11; 8]);
|
||||
handshake.push(20);
|
||||
handshake.extend([0; 20]);
|
||||
parse(&mut s, &handshake, true, 3);
|
||||
assert_eq!(s.transactions.len(), count);
|
||||
assert_eq!(
|
||||
s.encryption_flags(EncryptionHandling::ENCRYPTION_HANDLING_TRACK_ONLY),
|
||||
crate::applayer::APP_LAYER_PARSER_NO_INSPECTION_PAYLOAD
|
||||
);
|
||||
assert_eq!(
|
||||
s.encryption_flags(EncryptionHandling::ENCRYPTION_HANDLING_FULL),
|
||||
0
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue