diff --git a/etc/schema.json b/etc/schema.json index 24e9da1d1a02..489d26445852 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -1172,6 +1172,13 @@ "type": "string" } }, + "NS": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, "NULL": { "type": "array", "minItems": 1, @@ -3781,6 +3788,9 @@ "description": "Errors encountered parsing DNS/UDP protocol", "$ref": "#/$defs/stats_applayer_error" }, + "doh2": { + "$ref": "#/$defs/stats_applayer_error" + }, "enip_tcp": { "description": "Errors encounterd parsing ENIP/TCP", "$ref": "#/$defs/stats_applayer_error" @@ -3936,6 +3946,9 @@ "description": "Number of flows for DNS/UDP protocol", "type": "integer" }, + "doh2": { + "type": "integer" + }, "enip_tcp": { "description": "Number of flows for ENIP/TCP", "type": "integer" @@ -4090,6 +4103,9 @@ "dns_udp": { "type": "integer" }, + "doh2": { + "type": "integer" + }, "enip_tcp": { "type": "integer" }, diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index 522143fb6d0e..8dc5a1051c22 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -449,6 +449,7 @@ pub unsafe fn AppLayerRegisterParser(parser: *const RustParser, alproto: AppProt // Defined in app-layer-detect-proto.h extern { + pub fn AppLayerForceProtocolChange(f: *const Flow, new_proto: AppProto); pub fn AppLayerProtoDetectPPRegister(ipproto: u8, portstr: *const c_char, alproto: AppProto, min_depth: u16, max_depth: u16, dir: u8, pparser1: ProbeFn, pparser2: ProbeFn); diff --git a/rust/src/core.rs b/rust/src/core.rs index abb27ea578fe..bca228131124 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -61,6 +61,13 @@ impl Direction { pub fn is_to_client(&self) -> bool { matches!(self, Self::ToClient) } + + pub fn index(&self) -> usize { + match self { + Self::ToClient => 0, + _ => 1, + } + } } impl Default for Direction { diff --git a/rust/src/dns/dns.rs b/rust/src/dns/dns.rs index d4f71d268273..2735c7e1e391 100644 --- a/rust/src/dns/dns.rs +++ b/rust/src/dns/dns.rs @@ -271,6 +271,11 @@ impl DNSTransaction { } return 0; } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: DNSEvent) { + self.tx_data.set_event(event as u8); + } } struct ConfigTracker { @@ -328,6 +333,111 @@ impl State for DNSState { } } +fn dns_validate_header(input: &[u8]) -> Option<(&[u8], DNSHeader)> { + if let Ok((body, header)) = parser::dns_parse_header(input) { + if probe_header_validity(&header, input.len()).0 { + return Some((body, header)); + } + } + None +} + +#[derive(Debug, PartialEq, Eq)] +pub enum DNSParseError { + HeaderValidation, + NotRequest, + Incomplete, + OtherError, +} + +pub(crate) fn dns_parse_request(input: &[u8]) -> Result { + let (body, header) = if let Some((body, header)) = dns_validate_header(input) { + (body, header) + } else { + return Err(DNSParseError::HeaderValidation); + }; + + match parser::dns_parse_body(body, input, header) { + Ok((_, request)) => { + if request.header.flags & 0x8000 != 0 { + SCLogDebug!("DNS message is not a request"); + return Err(DNSParseError::NotRequest); + } + + let z_flag = request.header.flags & 0x0040 != 0; + let opcode = ((request.header.flags >> 11) & 0xf) as u8; + + let mut tx = DNSTransaction::new(Direction::ToServer); + tx.request = Some(request); + + if z_flag { + SCLogDebug!("Z-flag set on DNS request"); + tx.set_event(DNSEvent::ZFlagSet); + } + if opcode >= 7 { + tx.set_event(DNSEvent::InvalidOpcode); + } + + return Ok(tx); + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS request"); + return Err(DNSParseError::Incomplete); + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS request"); + return Err(DNSParseError::OtherError); + } + } +} + +pub(crate) fn dns_parse_response(input: &[u8]) -> Result { + let (body, header) = if let Some((body, header)) = dns_validate_header(input) { + (body, header) + } else { + return Err(DNSParseError::HeaderValidation); + }; + + match parser::dns_parse_body(body, input, header) { + Ok((_, response)) => { + SCLogDebug!("Response header flags: {}", response.header.flags); + let z_flag = response.header.flags & 0x0040 != 0; + let opcode = ((response.header.flags >> 11) & 0xf) as u8; + let flags = response.header.flags; + + let mut tx = DNSTransaction::new(Direction::ToClient); + tx.response = Some(response); + + if flags & 0x8000 == 0 { + SCLogDebug!("DNS message is not a response"); + tx.set_event(DNSEvent::NotResponse); + } + + if z_flag { + SCLogDebug!("Z-flag set on DNS response"); + tx.set_event(DNSEvent::ZFlagSet); + } + if opcode >= 7 { + tx.set_event(DNSEvent::InvalidOpcode); + } + + return Ok(tx); + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS request"); + return Err(DNSParseError::Incomplete); + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS request"); + return Err(DNSParseError::OtherError); + } + } +} + impl DNSState { pub fn new() -> Self { Default::default() @@ -372,60 +482,31 @@ impl DNSState { tx.tx_data.set_event(event as u8); } - fn validate_header<'a>(&self, input: &'a [u8]) -> Option<(&'a [u8], DNSHeader)> { - if let Ok((body, header)) = parser::dns_parse_header(input) { - if probe_header_validity(&header, input.len()).0 { - return Some((body, header)); - } - } - None - } - fn parse_request(&mut self, input: &[u8], is_tcp: bool) -> bool { - let (body, header) = if let Some((body, header)) = self.validate_header(input) { - (body, header) - } else { - return !is_tcp; - }; - - match parser::dns_parse_body(body, input, header) { - Ok((_, request)) => { - if request.header.flags & 0x8000 != 0 { - SCLogDebug!("DNS message is not a request"); + match dns_parse_request(input) { + Ok(mut tx) => { + self.tx_id += 1; + tx.id = self.tx_id; + self.transactions.push_back(tx); + return true; + } + Err(e) => match e { + DNSParseError::HeaderValidation => { + return !is_tcp; + } + DNSParseError::NotRequest => { self.set_event(DNSEvent::NotRequest); return false; } - - let z_flag = request.header.flags & 0x0040 != 0; - let opcode = ((request.header.flags >> 11) & 0xf) as u8; - - let mut tx = self.new_tx(Direction::ToServer); - tx.request = Some(request); - self.transactions.push_back(tx); - - if z_flag { - SCLogDebug!("Z-flag set on DNS response"); - self.set_event(DNSEvent::ZFlagSet); + DNSParseError::Incomplete => { + self.set_event(DNSEvent::MalformedData); + return false; } - - if opcode >= 7 { - self.set_event(DNSEvent::InvalidOpcode); + DNSParseError::OtherError => { + self.set_event(DNSEvent::MalformedData); + return false; } - - return true; - } - Err(Err::Incomplete(_)) => { - // Insufficient data. - SCLogDebug!("Insufficient data while parsing DNS request"); - self.set_event(DNSEvent::MalformedData); - return false; - } - Err(_) => { - // Error, probably malformed data. - SCLogDebug!("An error occurred while parsing DNS request"); - self.set_event(DNSEvent::MalformedData); - return false; - } + }, } } @@ -454,56 +535,29 @@ impl DNSState { } pub fn parse_response(&mut self, input: &[u8], is_tcp: bool) -> bool { - let (body, header) = if let Some((body, header)) = self.validate_header(input) { - (body, header) - } else { - return !is_tcp; - }; - - match parser::dns_parse_body(body, input, header) { - Ok((_, response)) => { - SCLogDebug!("Response header flags: {}", response.header.flags); - - if response.header.flags & 0x8000 == 0 { - SCLogDebug!("DNS message is not a response"); - self.set_event(DNSEvent::NotResponse); - } - - let z_flag = response.header.flags & 0x0040 != 0; - let opcode = ((response.header.flags >> 11) & 0xf) as u8; - - let mut tx = self.new_tx(Direction::ToClient); + match dns_parse_response(input) { + Ok(mut tx) => { + self.tx_id += 1; + tx.id = self.tx_id; if let Some(ref mut config) = &mut self.config { - if let Some(config) = config.remove(&response.header.tx_id) { - tx.tx_data.config = config; + if let Some(response) = &tx.response { + if let Some(config) = config.remove(&response.header.tx_id) { + tx.tx_data.config = config; + } } } - tx.response = Some(response); self.transactions.push_back(tx); - - if z_flag { - SCLogDebug!("Z-flag set on DNS response"); - self.set_event(DNSEvent::ZFlagSet); - } - - if opcode >= 7 { - self.set_event(DNSEvent::InvalidOpcode); - } - return true; } - Err(Err::Incomplete(_)) => { - // Insufficient data. - SCLogDebug!("Insufficient data while parsing DNS response"); - self.set_event(DNSEvent::MalformedData); - return false; - } - Err(_) => { - // Error, probably malformed data. - SCLogDebug!("An error occurred while parsing DNS response"); - self.set_event(DNSEvent::MalformedData); - return false; - } + Err(e) => match e { + DNSParseError::HeaderValidation => { + return !is_tcp; + } + _ => { + self.set_event(DNSEvent::MalformedData); + return false; + } + }, } } diff --git a/rust/src/dns/log.rs b/rust/src/dns/log.rs index b2bf72ba1e46..745f4a16936f 100644 --- a/rust/src/dns/log.rs +++ b/rust/src/dns/log.rs @@ -610,7 +610,7 @@ fn dns_log_json_answer( } fn dns_log_query( - tx: &mut DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, + tx: &DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, ) -> Result { let index = i as usize; if let Some(request) = &tx.request { @@ -637,7 +637,7 @@ fn dns_log_query( #[no_mangle] pub extern "C" fn rs_dns_log_json_query( - tx: &mut DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, + tx: &DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, ) -> bool { match dns_log_query(tx, i, flags, jb) { Ok(false) | Err(_) => { @@ -651,7 +651,7 @@ pub extern "C" fn rs_dns_log_json_query( #[no_mangle] pub extern "C" fn rs_dns_log_json_answer( - tx: &mut DNSTransaction, flags: u64, js: &mut JsonBuilder, + tx: &DNSTransaction, flags: u64, js: &mut JsonBuilder, ) -> bool { if let Some(response) = &tx.response { for query in &response.queries { @@ -664,7 +664,7 @@ pub extern "C" fn rs_dns_log_json_answer( } #[no_mangle] -pub extern "C" fn rs_dns_do_log_answer(tx: &mut DNSTransaction, flags: u64) -> bool { +pub extern "C" fn rs_dns_do_log_answer(tx: &DNSTransaction, flags: u64) -> bool { if let Some(response) = &tx.response { for query in &response.queries { if dns_log_rrtype_enabled(query.rrtype, flags) { diff --git a/rust/src/http2/decompression.rs b/rust/src/http2/decompression.rs index 99f8af39032c..31e8547a8134 100644 --- a/rust/src/http2/decompression.rs +++ b/rust/src/http2/decompression.rs @@ -178,10 +178,12 @@ impl HTTP2DecoderHalf { if self.encoding == HTTP2ContentEncoding::Unknown { if input == b"gzip" { self.encoding = HTTP2ContentEncoding::Gzip; - self.decoder = HTTP2Decompresser::Gzip(Box::new(GzDecoder::new(HTTP2cursor::new()))); + self.decoder = + HTTP2Decompresser::Gzip(Box::new(GzDecoder::new(HTTP2cursor::new()))); } else if input == b"deflate" { self.encoding = HTTP2ContentEncoding::Deflate; - self.decoder = HTTP2Decompresser::Deflate(Box::new(DeflateDecoder::new(HTTP2cursor::new()))); + self.decoder = + HTTP2Decompresser::Deflate(Box::new(DeflateDecoder::new(HTTP2cursor::new()))); } else if input == b"br" { self.encoding = HTTP2ContentEncoding::Br; self.decoder = HTTP2Decompresser::Brotli(Box::new(brotli::Decompressor::new( diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index 52b41190555b..d2be3fe94241 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -537,7 +537,7 @@ fn http2_tx_get_resp_line(tx: &mut HTTP2Transaction) { return; } let empty = Vec::new(); - let mut resp_line : Vec = Vec::new(); + let mut resp_line: Vec = Vec::new(); let status = if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToClient, ":status") { @@ -616,7 +616,7 @@ fn http2_lower(value: &[u8]) -> Option> { fn http2_normalize_host(value: &[u8]) -> &[u8] { match value.iter().position(|&x| x == b'@') { Some(i) => { - let value = &value[i+1..]; + let value = &value[i + 1..]; match value.iter().position(|&x| x == b':') { Some(i) => { return &value[..i]; @@ -626,16 +626,14 @@ fn http2_normalize_host(value: &[u8]) -> &[u8] { } } } - None => { - match value.iter().position(|&x| x == b':') { - Some(i) => { - return &value[..i]; - } - None => { - return value; - } + None => match value.iter().position(|&x| x == b':') { + Some(i) => { + return &value[..i]; } - } + None => { + return value; + } + }, } } diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 1a45881949c6..e283861a005e 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -25,6 +25,9 @@ use crate::conf::conf_get; use crate::core::*; use crate::filecontainer::*; use crate::filetracker::*; + +use crate::dns::dns::{dns_parse_request, dns_parse_response, DNSTransaction}; + use nom7::Err; use std; use std::collections::VecDeque; @@ -33,6 +36,7 @@ use std::fmt; use std::io; static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN; +static mut ALPROTO_DOH2: AppProto = ALPROTO_UNKNOWN; const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384; const HTTP2_MAX_HANDLED_FRAME_SIZE: usize = 65536; @@ -61,7 +65,7 @@ const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4; const HTTP2_FRAME_PRIORITY_LEN: usize = 5; const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4; pub static mut HTTP2_MAX_TABLESIZE: u32 = 65536; // 0x10000 -// maximum size of reassembly for header + continuation + // maximum size of reassembly for header + continuation static mut HTTP2_MAX_REASS: usize = 102400; static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000 @@ -146,6 +150,12 @@ pub struct HTTP2Transaction { pub escaped: Vec>, pub req_line: Vec, pub resp_line: Vec, + + is_doh_data: [bool; 2], + // dns response buffer + pub doh_data_buf: [Vec; 2], + pub dns_request_tx: Option, + pub dns_response_tx: Option, } impl Transaction for HTTP2Transaction { @@ -177,6 +187,10 @@ impl HTTP2Transaction { escaped: Vec::with_capacity(16), req_line: Vec::new(), resp_line: Vec::new(), + is_doh_data: [false; 2], + doh_data_buf: Default::default(), + dns_request_tx: None, + dns_response_tx: None, } } @@ -186,13 +200,13 @@ impl HTTP2Transaction { if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { //TODO get a file container instead of NULL (c.HTPFileCloseHandleRange)( - sfcm.files_sbcfg, - std::ptr::null_mut(), - 0, - self.file_range, - std::ptr::null_mut(), - 0, - ); + sfcm.files_sbcfg, + std::ptr::null_mut(), + 0, + self.file_range, + std::ptr::null_mut(), + 0, + ); (c.HttpRangeFreeBlock)(self.file_range); self.file_range = std::ptr::null_mut(); } @@ -204,12 +218,27 @@ impl HTTP2Transaction { self.tx_data.set_event(event as u8); } - fn handle_headers(&mut self, blocks: &[parser::HTTP2FrameHeaderBlock], dir: Direction) { + fn handle_headers( + &mut self, blocks: &[parser::HTTP2FrameHeaderBlock], dir: Direction, + ) -> Option> { let mut authority = None; + let mut path = None; + let mut doh = false; let mut host = None; for block in blocks { if block.name == b"content-encoding" { self.decoder.http2_encoding_fromvec(&block.value, dir); + } else if block.name == b"accept" { + //TODO? faster pattern matching + if block.value == b"application/dns-message" { + doh = true; + } + } else if block.name == b"content-type" { + if block.value == b"application/dns-message" { + self.is_doh_data[dir.index()] = true; + } + } else if block.name == b":path" { + path = Some(&block.value); } else if block.name.eq_ignore_ascii_case(b":authority") { authority = Some(&block.value); if block.value.iter().any(|&x| x == b'@') { @@ -231,6 +260,14 @@ impl HTTP2Transaction { } } } + if doh && unsafe { ALPROTO_DOH2 } != ALPROTO_UNKNOWN { + if let Some(p) = path { + if let Ok((_, dns_req)) = parser::doh_extract_request(p) { + return Some(dns_req); + } + } + } + return None; } pub fn update_file_flags(&mut self, flow_file_flags: u16) { @@ -239,10 +276,10 @@ impl HTTP2Transaction { } fn decompress<'a>( - &'a mut self, input: &'a [u8], dir: Direction, sfcm: &'static SuricataFileContext, over: bool, flow: *const Flow, + &'a mut self, input: &'a [u8], output: &'a mut Vec, dir: Direction, + sfcm: &'static SuricataFileContext, over: bool, flow: *const Flow, ) -> io::Result<()> { - let mut output = Vec::with_capacity(decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE); - let decompressed = self.decoder.decompress(input, &mut output, dir)?; + let decompressed = self.decoder.decompress(input, output, dir)?; let xid: u32 = self.tx_id as u32; if dir == Direction::ToClient { self.ft_tc.tx_id = self.tx_id - 1; @@ -258,7 +295,14 @@ impl HTTP2Transaction { ) { match range::http2_parse_check_content_range(&value) { Ok((_, v)) => { - range::http2_range_open(self, &v, flow, sfcm, Direction::ToClient, decompressed); + range::http2_range_open( + self, + &v, + flow, + sfcm, + Direction::ToClient, + decompressed, + ); if over && !self.file_range.is_null() { range::http2_range_close(self, Direction::ToClient, &[]) } @@ -301,13 +345,21 @@ impl HTTP2Transaction { &xid, ); }; + if unsafe { ALPROTO_DOH2 } != ALPROTO_UNKNOWN { + // we store DNS response, and process it when complete + if self.is_doh_data[dir.index()] && self.doh_data_buf[dir.index()].len() < 0xFFFF { + // a DNS message is U16_MAX + self.doh_data_buf[dir.index()].extend_from_slice(decompressed); + } + } return Ok(()); } fn handle_frame( &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction, - ) { + ) -> Option> { //handle child_stream_id changes + let mut r = None; match data { HTTP2FrameTypeData::PUSHPROMISE(hs) => { if dir == Direction::ToClient { @@ -317,7 +369,7 @@ impl HTTP2Transaction { } self.state = HTTP2TransactionState::HTTP2StateReserved; } - self.handle_headers(&hs.blocks, dir); + r = self.handle_headers(&hs.blocks, dir); } HTTP2FrameTypeData::CONTINUATION(hs) => { if dir == Direction::ToClient @@ -325,13 +377,13 @@ impl HTTP2Transaction { { self.child_stream_id = 0; } - self.handle_headers(&hs.blocks, dir); + r = self.handle_headers(&hs.blocks, dir); } HTTP2FrameTypeData::HEADERS(hs) => { if dir == Direction::ToClient { self.child_stream_id = 0; } - self.handle_headers(&hs.blocks, dir); + r = self.handle_headers(&hs.blocks, dir); } HTTP2FrameTypeData::RSTSTREAM(_) => { self.child_stream_id = 0; @@ -378,6 +430,27 @@ impl HTTP2Transaction { } _ => {} } + return r; + } + + fn handle_dns_data(&mut self, over: bool, dir: Direction, flow: *const Flow) { + if !self.doh_data_buf[dir.index()].is_empty() && over { + if dir.is_to_client() { + if let Ok(mut dtx) = dns_parse_response(&self.doh_data_buf[dir.index()]) { + dtx.id = 1; + self.dns_response_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange(flow, ALPROTO_DOH2); + } + } + } else if let Ok(mut dtx) = dns_parse_request(&self.doh_data_buf[dir.index()]) { + dtx.id = 1; + self.dns_request_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange(flow, ALPROTO_DOH2); + } + } + } } } @@ -603,7 +676,7 @@ impl HTTP2State { tx.state = HTTP2TransactionState::HTTP2StateGlobal; tx.tx_data.update_file_flags(self.state_data.file_flags); // TODO can this tx hold files? - tx.tx_data.file_tx = STREAM_TOSERVER|STREAM_TOCLIENT; // might hold files in both directions + tx.tx_data.file_tx = STREAM_TOSERVER | STREAM_TOCLIENT; // might hold files in both directions tx.update_file_flags(tx.tx_data.file_flags); self.transactions.push_back(tx); return self.transactions.back_mut().unwrap(); @@ -677,7 +750,7 @@ impl HTTP2State { tx.state = HTTP2TransactionState::HTTP2StateOpen; tx.tx_data.update_file_flags(self.state_data.file_flags); tx.update_file_flags(tx.tx_data.file_flags); - tx.tx_data.file_tx = STREAM_TOSERVER|STREAM_TOCLIENT; // might hold files in both directions + tx.tx_data.file_tx = STREAM_TOSERVER | STREAM_TOCLIENT; // might hold files in both directions self.transactions.push_back(tx); return Some(self.transactions.back_mut().unwrap()); } @@ -688,9 +761,7 @@ impl HTTP2State { for block in blocks { if block.error >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError { self.set_event(HTTP2Event::InvalidHeader); - } else if block.error - == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate - { + } else if block.error == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate { update = true; if block.sizeupdate > sizeup { sizeup = block.sizeupdate; @@ -891,13 +962,15 @@ impl HTTP2State { *reass_limit_reached = true; } if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { - let hs = parser::HTTP2FrameContinuation { - blocks: Vec::new(), - }; + let hs = parser::HTTP2FrameContinuation { blocks: Vec::new() }; return HTTP2FrameTypeData::CONTINUATION(hs); } } // else try to parse anyways - let input_reass = if head.stream_id == buf.stream_id { &buf.data } else { input }; + let input_reass = if head.stream_id == buf.stream_id { + &buf.data + } else { + input + }; let dyn_headers = if dir == Direction::ToClient { &mut self.dynamic_headers_tc @@ -1055,8 +1128,18 @@ impl HTTP2State { return AppLayerResult::err(); } let tx = tx.unwrap(); + if let Some(doh_req_buf) = tx.handle_frame(&head, &txdata, dir) { + if let Ok(mut dtx) = dns_parse_request(&doh_req_buf) { + dtx.id = 1; + tx.dns_request_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange(flow, ALPROTO_DOH2); + } + } + } if reass_limit_reached { - tx.tx_data.set_event(HTTP2Event::ReassemblyLimitReached as u8); + tx.tx_data + .set_event(HTTP2Event::ReassemblyLimitReached as u8); } tx.handle_frame(&head, &txdata, dir); let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; @@ -1087,16 +1170,26 @@ impl HTTP2State { tx_same.ft_ts.tx_id = tx_same.tx_id - 1; }; let mut dinput = &rem[..hlsafe]; - if padded && !rem.is_empty() && usize::from(rem[0]) < hlsafe{ + if padded && !rem.is_empty() && usize::from(rem[0]) < hlsafe { dinput = &rem[1..hlsafe - usize::from(rem[0])]; } - if tx_same.decompress( + let mut output = Vec::with_capacity( + decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE, + ); + match tx_same.decompress( dinput, + &mut output, dir, sfcm, over, - flow).is_err() { - self.set_event(HTTP2Event::FailedDecompression); + flow, + ) { + Ok(_) => { + tx_same.handle_dns_data(over, dir, flow); + } + _ => { + self.set_event(HTTP2Event::FailedDecompression); + } } } } @@ -1191,6 +1284,22 @@ impl HTTP2State { // C exports. +#[no_mangle] +pub unsafe extern "C" fn SCDoH2GetDnsTx( + tx: &HTTP2Transaction, flags: u8, +) -> *mut std::os::raw::c_void { + if flags & Direction::ToServer as u8 != 0 { + if let Some(ref dtx) = &tx.dns_request_tx { + return dtx as *const _ as *mut _; + } + } else if flags & Direction::ToClient as u8 != 0 { + if let Some(ref dtx) = &tx.dns_response_tx { + return dtx as *const _ as *mut _; + } + } + std::ptr::null_mut() +} + export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction); export_state_data_get!(rs_http2_get_state_data, HTTP2State); @@ -1319,15 +1428,20 @@ pub unsafe extern "C" fn rs_http2_tx_get_alstate_progress( #[no_mangle] pub unsafe extern "C" fn rs_http2_getfiles( - _state: *mut std::os::raw::c_void, - tx: *mut std::os::raw::c_void, direction: u8, + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8, ) -> AppLayerGetFileState { let tx = cast_pointer!(tx, HTTP2Transaction); if let Some(sfcm) = { SURICATA_HTTP2_FILE_CONFIG } { if direction & STREAM_TOSERVER != 0 { - return AppLayerGetFileState { fc: &mut tx.ft_ts.file, cfg: sfcm.files_sbcfg } + return AppLayerGetFileState { + fc: &mut tx.ft_ts.file, + cfg: sfcm.files_sbcfg, + }; } else { - return AppLayerGetFileState { fc: &mut tx.ft_tc.file, cfg: sfcm.files_sbcfg } + return AppLayerGetFileState { + fc: &mut tx.ft_tc.file, + cfg: sfcm.files_sbcfg, + }; } } AppLayerGetFileState::err() @@ -1339,7 +1453,7 @@ const PARSER_NAME: &[u8] = b"http2\0"; #[no_mangle] pub unsafe extern "C" fn rs_http2_register_parser() { let default_port = CString::new("[80]").unwrap(); - let parser = RustParser { + let mut parser = RustParser { name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, default_port: default_port.as_ptr(), ipproto: IPPROTO_TCP, @@ -1406,4 +1520,22 @@ pub unsafe extern "C" fn rs_http2_register_parser() { } else { SCLogNotice!("Protocol detector and parser disabled for HTTP2."); } + + // doh2 is just http2 wrapped in another name + parser.name = b"doh2\0".as_ptr() as *const std::os::raw::c_char; + parser.probe_tc = None; + parser.default_port = std::ptr::null(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DOH2 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } else { + SCLogWarning!("DOH2 is not meant to be detection-only."); + } + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DOH2); + SCLogDebug!("Rust doh2 parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for DOH2."); + } } diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index 099112b1aeb4..38a5ee4d178a 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -17,6 +17,7 @@ use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction}; use super::parser; +use crate::dns::log::{rs_dns_do_log_answer, rs_dns_log_json_answer, rs_dns_log_json_query}; use crate::jsonbuilder::{JsonBuilder, JsonError}; use std; use std::collections::HashMap; @@ -112,7 +113,10 @@ fn log_http2_frames(frames: &[HTTP2Frame], js: &mut JsonBuilder) -> Result Result( while dyn_headers.current_size > dyn_headers.max_size && toremove < dyn_headers.table.len() { - dyn_headers.current_size -= - 32 + dyn_headers.table[toremove].name.len() + dyn_headers.table[toremove].value.len(); + dyn_headers.current_size -= 32 + + dyn_headers.table[toremove].name.len() + + dyn_headers.table[toremove].value.len(); toremove += 1; } dyn_headers.table.drain(0..toremove); @@ -755,6 +757,19 @@ pub fn http2_parse_frame_settings(i: &[u8]) -> IResult<&[u8], Vec IResult<&[u8], Vec> { + let (i, _) = tag("/dns-query?dns=")(i)?; + match base64::decode(i) { + Ok(dec) => { + // i is unused + return Ok((i, dec)); + } + _ => { + return Err(Err::Error(make_error(i, ErrorKind::MapOpt))); + } + } +} + #[cfg(test)] mod tests { diff --git a/rust/src/http2/range.rs b/rust/src/http2/range.rs index 9c96899443a1..71c22a7b5d01 100644 --- a/rust/src/http2/range.rs +++ b/rust/src/http2/range.rs @@ -131,7 +131,11 @@ pub fn http2_range_open( // whole file in one range return; } - let flags = if dir == Direction::ToServer { tx.ft_ts.file_flags } else { tx.ft_tc.file_flags }; + let flags = if dir == Direction::ToServer { + tx.ft_ts.file_flags + } else { + tx.ft_tc.file_flags + }; if let Ok((key, index)) = http2_range_key_get(tx) { let name = &key[index..]; tx.file_range = unsafe { @@ -151,15 +155,15 @@ pub fn http2_range_open( } } -pub fn http2_range_append(cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8]) { +pub fn http2_range_append( + cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8], +) { unsafe { HttpRangeAppendData(cfg.files_sbcfg, fr, data.as_ptr(), data.len() as u32); } } -pub fn http2_range_close( - tx: &mut HTTP2Transaction, dir: Direction, data: &[u8], -) { +pub fn http2_range_close(tx: &mut HTTP2Transaction, dir: Direction, data: &[u8]) { let added = if let Some(c) = unsafe { SC } { if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { let (files, flags) = if dir == Direction::ToServer { @@ -168,13 +172,13 @@ pub fn http2_range_close( (&mut tx.ft_tc.file, tx.ft_tc.file_flags) }; let added = (c.HTPFileCloseHandleRange)( - sfcm.files_sbcfg, - files, - flags, - tx.file_range, - data.as_ptr(), - data.len() as u32, - ); + sfcm.files_sbcfg, + files, + flags, + tx.file_range, + data.as_ptr(), + data.len() as u32, + ); (c.HttpRangeFreeBlock)(tx.file_range); added } else { @@ -197,7 +201,8 @@ extern "C" { data: *const c_uchar, data_len: u32, ) -> *mut HttpRangeContainerBlock; pub fn HttpRangeAppendData( - cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, data_len: u32, + cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, + data_len: u32, ) -> std::os::raw::c_int; } diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index c47a437659fd..fd2c1a17bdc9 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -1844,6 +1844,16 @@ bool AppLayerRequestProtocolTLSUpgrade(Flow *f) return AppLayerRequestProtocolChange(f, 443, ALPROTO_TLS); } +void AppLayerForceProtocolChange(Flow *f, AppProto new_proto) +{ + if (new_proto != f->alproto) { + f->alproto_orig = f->alproto; + f->alproto = new_proto; + f->alproto_ts = f->alproto; + f->alproto_tc = f->alproto; + } +} + void AppLayerProtoDetectReset(Flow *f) { FLOW_RESET_PM_DONE(f, STREAM_TOSERVER); @@ -2025,6 +2035,9 @@ void AppLayerProtoDetectSupportedIpprotos(AppProto alproto, uint8_t *ipprotos) if (alproto == ALPROTO_HTTP) { AppLayerProtoDetectSupportedIpprotos(ALPROTO_HTTP1, ipprotos); AppLayerProtoDetectSupportedIpprotos(ALPROTO_HTTP2, ipprotos); + } else if (alproto == ALPROTO_DOH2) { + // DOH2 is not detected, just HTTP2 + AppLayerProtoDetectSupportedIpprotos(ALPROTO_HTTP2, ipprotos); } else { AppLayerProtoDetectPMGetIpprotos(alproto, ipprotos); AppLayerProtoDetectPPGetIpprotos(alproto, ipprotos); diff --git a/src/app-layer-detect-proto.h b/src/app-layer-detect-proto.h index 158ad234dd49..adc458ed93f2 100644 --- a/src/app-layer-detect-proto.h +++ b/src/app-layer-detect-proto.h @@ -121,6 +121,8 @@ void AppLayerProtoDetectReset(Flow *); bool AppLayerRequestProtocolChange(Flow *f, uint16_t dp, AppProto expect_proto); bool AppLayerRequestProtocolTLSUpgrade(Flow *f); +void AppLayerForceProtocolChange(Flow *f, AppProto new_proto); + /** * \brief Cleans up the app layer protocol detection phase. */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index b6e1b73d08d4..d25c4c82d589 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -61,6 +61,7 @@ const AppProtoStringTuple AppProtoStrings[ALPROTO_MAX] = { { ALPROTO_PGSQL, "pgsql" }, { ALPROTO_TELNET, "telnet" }, { ALPROTO_WEBSOCKET, "websocket" }, + { ALPROTO_DOH2, "doh2" }, { ALPROTO_TEMPLATE, "template" }, { ALPROTO_RDP, "rdp" }, { ALPROTO_HTTP2, "http2" }, diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 5c27255a7b46..b83cd915f0ec 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -57,6 +57,7 @@ enum AppProtoEnum { ALPROTO_PGSQL, ALPROTO_TELNET, ALPROTO_WEBSOCKET, + ALPROTO_DOH2, ALPROTO_TEMPLATE, ALPROTO_RDP, ALPROTO_HTTP2, @@ -85,6 +86,16 @@ static inline bool AppProtoIsValid(AppProto a) return ((a > ALPROTO_UNKNOWN && a < ALPROTO_FAILED)); } +// whether an engine proto works on a flow proto +static inline bool AppProtoCompatible(AppProto eng_proto, AppProto alproto) +{ + switch (alproto) { + case ALPROTO_DOH2: + return (eng_proto == ALPROTO_HTTP2) || (eng_proto == ALPROTO_DNS); + } + return (eng_proto == alproto); +} + // whether a signature AppProto matches a flow (or signature) AppProto static inline bool AppProtoEquals(AppProto sigproto, AppProto alproto) { @@ -92,6 +103,13 @@ static inline bool AppProtoEquals(AppProto sigproto, AppProto alproto) return true; } switch (sigproto) { + case ALPROTO_DNS: + return (alproto == ALPROTO_DOH2) || (alproto == ALPROTO_DNS); + case ALPROTO_HTTP2: + return (alproto == ALPROTO_DOH2) || (alproto == ALPROTO_HTTP2); + case ALPROTO_DOH2: + return (alproto == ALPROTO_DOH2) || (alproto == ALPROTO_HTTP2) || + (alproto == ALPROTO_DNS) || (alproto == ALPROTO_HTTP); case ALPROTO_HTTP: return (alproto == ALPROTO_HTTP1) || (alproto == ALPROTO_HTTP2); case ALPROTO_DCERPC: @@ -100,6 +118,48 @@ static inline bool AppProtoEquals(AppProto sigproto, AppProto alproto) return false; } +// whether a signature AppProto matches a flow (or signature) AppProto +static inline AppProto AppProtoCommon(AppProto sigproto, AppProto alproto) +{ + switch (sigproto) { + case ALPROTO_SMB: + if (alproto == ALPROTO_DCERPC) { + // ok to have dcerpc keywords in smb sig + return ALPROTO_SMB; + } + break; + case ALPROTO_HTTP: + // we had a generic http sig, now version specific + if (alproto == ALPROTO_HTTP1) { + return ALPROTO_HTTP1; + } else if (alproto == ALPROTO_HTTP2) { + return ALPROTO_HTTP2; + } + break; + case ALPROTO_HTTP1: + // version-specific sig with a generic keyword + if (alproto == ALPROTO_HTTP) { + return ALPROTO_HTTP1; + } + break; + case ALPROTO_HTTP2: + if (alproto == ALPROTO_HTTP) { + return ALPROTO_HTTP2; + } + break; + case ALPROTO_DOH2: + // DOH2 accepts different protocol keywords + if (alproto == ALPROTO_HTTP || alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { + return ALPROTO_DOH2; + } + break; + } + if (sigproto != alproto) { + return ALPROTO_FAILED; + } + return alproto; +} + /** * \brief Maps the ALPROTO_*, to its string equivalent. * diff --git a/src/app-layer.c b/src/app-layer.c index 2566861025fc..ec3b2598beac 100644 --- a/src/app-layer.c +++ b/src/app-layer.c @@ -555,7 +555,7 @@ static int TCPProtoDetect(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, PACKET_PROFILING_APP_START(app_tctx, f->alproto); int r = AppLayerParserParse(tv, app_tctx->alp_tctx, f, f->alproto, flags, data, data_len); - PACKET_PROFILING_APP_END(app_tctx, f->alproto); + PACKET_PROFILING_APP_END(app_tctx); p->app_update_direction = (uint8_t)dir; if (r != 1) { StreamTcpUpdateAppLayerProgress(ssn, direction, data_len); @@ -643,7 +643,7 @@ static int TCPProtoDetect(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, int r = AppLayerParserParse(tv, app_tctx->alp_tctx, f, f->alproto, flags, data, data_len); - PACKET_PROFILING_APP_END(app_tctx, f->alproto); + PACKET_PROFILING_APP_END(app_tctx); p->app_update_direction = (uint8_t)dir; if (r != 1) { StreamTcpUpdateAppLayerProgress(ssn, direction, data_len); @@ -752,7 +752,7 @@ int AppLayerHandleTCPData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, Packet PACKET_PROFILING_APP_START(app_tctx, f->alproto); r = AppLayerParserParse(tv, app_tctx->alp_tctx, f, f->alproto, flags, data, data_len); - PACKET_PROFILING_APP_END(app_tctx, f->alproto); + PACKET_PROFILING_APP_END(app_tctx); p->app_update_direction = (uint8_t)dir; /* ignore parser result for gap */ StreamTcpUpdateAppLayerProgress(ssn, direction, data_len); @@ -838,7 +838,7 @@ int AppLayerHandleTCPData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, Packet PACKET_PROFILING_APP_START(app_tctx, f->alproto); r = AppLayerParserParse(tv, app_tctx->alp_tctx, f, f->alproto, flags, data, data_len); - PACKET_PROFILING_APP_END(app_tctx, f->alproto); + PACKET_PROFILING_APP_END(app_tctx); p->app_update_direction = (uint8_t)dir; if (r != 1) { StreamTcpUpdateAppLayerProgress(ssn, direction, data_len); @@ -964,7 +964,7 @@ int AppLayerHandleUdp(ThreadVars *tv, AppLayerThreadCtx *tctx, Packet *p, Flow * PACKET_PROFILING_APP_START(tctx, f->alproto); r = AppLayerParserParse(tv, tctx->alp_tctx, f, f->alproto, flags, p->payload, p->payload_len); - PACKET_PROFILING_APP_END(tctx, f->alproto); + PACKET_PROFILING_APP_END(tctx); p->app_update_direction = (uint8_t)UPDATE_DIR_PACKET; } PACKET_PROFILING_APP_STORE(tctx, p); @@ -980,7 +980,7 @@ int AppLayerHandleUdp(ThreadVars *tv, AppLayerThreadCtx *tctx, Packet *p, Flow * PACKET_PROFILING_APP_START(tctx, f->alproto); r = AppLayerParserParse(tv, tctx->alp_tctx, f, f->alproto, flags, p->payload, p->payload_len); - PACKET_PROFILING_APP_END(tctx, f->alproto); + PACKET_PROFILING_APP_END(tctx); PACKET_PROFILING_APP_STORE(tctx, p); p->app_update_direction = (uint8_t)UPDATE_DIR_PACKET; } diff --git a/src/detect-dns-answer-name.c b/src/detect-dns-answer-name.c index bc64f55fbf49..3e46b4e82420 100644 --- a/src/detect-dns-answer-name.c +++ b/src/detect-dns-answer-name.c @@ -83,6 +83,12 @@ static uint8_t DetectEngineInspectCb(DetectEngineCtx *de_ctx, DetectEngineThread transforms = engine->v2.transforms; } + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + } for (uint32_t i = 0;; i++) { InspectionBuffer *buffer = GetBuffer(det_ctx, flags, transforms, txv, i, engine->sm_list); if (buffer == NULL || buffer->inspect == NULL) { @@ -108,6 +114,12 @@ static void PrefilterTx(DetectEngineThreadCtx *det_ctx, const void *pectx, Packe const MpmCtx *mpm_ctx = ctx->mpm_ctx; const int list_id = ctx->list_id; + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return; + } + } for (uint32_t i = 0;; i++) { InspectionBuffer *buffer = GetBuffer(det_ctx, flags, ctx->transforms, txv, i, list_id); if (buffer == NULL) { diff --git a/src/detect-dns-opcode.c b/src/detect-dns-opcode.c index f5dcab700f27..493098b016de 100644 --- a/src/detect-dns-opcode.c +++ b/src/detect-dns-opcode.c @@ -67,6 +67,12 @@ static int DetectDnsOpcodeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) { + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return 0; + } + } return rs_dns_opcode_match(txv, (void *)ctx, flags); } diff --git a/src/detect-dns-query-name.c b/src/detect-dns-query-name.c index a3983bf575cd..572ef3d31dc6 100644 --- a/src/detect-dns-query-name.c +++ b/src/detect-dns-query-name.c @@ -83,6 +83,12 @@ static uint8_t DetectEngineInspectCb(DetectEngineCtx *de_ctx, DetectEngineThread transforms = engine->v2.transforms; } + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + } for (uint32_t i = 0;; i++) { InspectionBuffer *buffer = GetBuffer(det_ctx, flags, transforms, txv, i, engine->sm_list); if (buffer == NULL || buffer->inspect == NULL) { @@ -108,6 +114,12 @@ static void PrefilterTx(DetectEngineThreadCtx *det_ctx, const void *pectx, Packe const MpmCtx *mpm_ctx = ctx->mpm_ctx; const int list_id = ctx->list_id; + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return; + } + } for (uint32_t i = 0;; i++) { InspectionBuffer *buffer = GetBuffer(det_ctx, flags, ctx->transforms, txv, i, list_id); if (buffer == NULL) { diff --git a/src/detect-dns-query.c b/src/detect-dns-query.c index 3225f126f2df..c29c9451ae74 100644 --- a/src/detect-dns-query.c +++ b/src/detect-dns-query.c @@ -108,6 +108,12 @@ static uint8_t DetectEngineInspectDnsQuery(DetectEngineCtx *de_ctx, DetectEngine transforms = engine->v2.transforms; } + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + } while(1) { struct DnsQueryGetDataArgs cbdata = { local_id, txv, }; InspectionBuffer *buffer = @@ -149,6 +155,12 @@ static void PrefilterTxDnsQuery(DetectEngineThreadCtx *det_ctx, const void *pect const int list_id = ctx->list_id; uint32_t local_id = 0; + if (f->alproto == ALPROTO_DOH2) { + txv = SCDoH2GetDnsTx(txv, flags); + if (txv == NULL) { + return; + } + } while(1) { // loop until we get a NULL diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 6ceeaa63f233..be62e6ce7d9f 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -107,6 +107,11 @@ void DetectAppLayerMpmRegister(const char *name, int direction, int priority, FatalError("MPM engine registration for %s failed", name); } + // every HTTP2 can be accessed from DOH2 + if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { + DetectAppLayerMpmRegister(name, direction, priority, PrefilterRegister, GetData, + ALPROTO_DOH2, tx_min_progress); + } DetectBufferMpmRegistry *am = SCCalloc(1, sizeof(*am)); BUG_ON(am == NULL); am->name = name; diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index e40a5175dafb..9611ba335f0d 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -107,7 +107,7 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, PrefilterEngine *engine = sgh->tx_engines; do { - if (engine->alproto != alproto) + if (!AppProtoCompatible(engine->alproto, alproto)) goto next; if (engine->ctx.tx_min_progress > tx->tx_progress) break; diff --git a/src/detect-engine.c b/src/detect-engine.c index 75a198e8fabd..0681c49ae465 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -195,6 +195,10 @@ void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uin } else { direction = 1; } + // every DNS or HTTP2 can be accessed from DOH2 + if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { + DetectAppLayerInspectEngineRegister(name, ALPROTO_DOH2, dir, progress, Callback, GetData); + } DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); diff --git a/src/detect-parse.c b/src/detect-parse.c index de898f556966..6bd445441f65 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1758,20 +1758,9 @@ int DetectSignatureSetAppProto(Signature *s, AppProto alproto) return -1; } - /* since AppProtoEquals is quite permissive wrt dcerpc and smb, make sure - * we refuse `alert dcerpc ... smb.share; content...` explicitly. */ - if (alproto == ALPROTO_SMB && s->alproto == ALPROTO_DCERPC) { - SCLogError("can't set rule app proto to %s: already set to %s", AppProtoToString(alproto), - AppProtoToString(s->alproto)); - return -1; - } - - if (s->alproto != ALPROTO_UNKNOWN && !AppProtoEquals(s->alproto, alproto)) { - if (AppProtoEquals(alproto, s->alproto)) { - // happens if alproto = HTTP_ANY and s->alproto = HTTP1 - // in this case, we must keep the most restrictive HTTP1 - alproto = s->alproto; - } else { + if (s->alproto != ALPROTO_UNKNOWN) { + alproto = AppProtoCommon(s->alproto, alproto); + if (alproto == ALPROTO_FAILED) { SCLogError("can't set rule app proto to %s: already set to %s", AppProtoToString(alproto), AppProtoToString(s->alproto)); return -1; diff --git a/src/detect.c b/src/detect.c index 9d595e66dc4a..019d8f05baca 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1110,7 +1110,9 @@ static bool DetectRunTxInspectRule(ThreadVars *tv, if (!(inspect_flags & BIT_U32(engine->id)) && direction == engine->dir) { - const bool skip_engine = (engine->alproto != 0 && engine->alproto != f->alproto); + const bool skip_engine = + (engine->alproto != 0 && !AppProtoCompatible(engine->alproto, f->alproto)); + /* special case: file_data on 'alert tcp' will have engines * in the list that are not for us. */ if (unlikely(skip_engine)) { diff --git a/src/output.c b/src/output.c index 79524785a716..0a0d70b9e0dd 100644 --- a/src/output.c +++ b/src/output.c @@ -1087,6 +1087,10 @@ void OutputRegisterLoggers(void) OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonWebSocketLog", "eve-log.websocket", OutputJsonLogInitSub, ALPROTO_WEBSOCKET, JsonGenericDirPacketLogger, JsonLogThreadInit, JsonLogThreadDeinit, NULL); + /* DoH2 JSON logger. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDoH2Log", "eve-log.doh2", + OutputJsonLogInitSub, ALPROTO_DOH2, JsonGenericDirFlowLogger, JsonLogThreadInit, + JsonLogThreadDeinit, NULL); /* Template JSON logger. */ OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonTemplateLog", "eve-log.template", OutputJsonLogInitSub, ALPROTO_TEMPLATE, JsonGenericDirPacketLogger, JsonLogThreadInit, @@ -1140,6 +1144,7 @@ static EveJsonSimpleAppLayerLogger simple_json_applayer_loggers[ALPROTO_MAX] = { { ALPROTO_PGSQL, JsonPgsqlAddMetadata }, { ALPROTO_TELNET, NULL }, // no logging { ALPROTO_WEBSOCKET, rs_websocket_logger_log }, + { ALPROTO_DOH2, rs_http2_log_json }, // http2 logger knows how to log dns { ALPROTO_TEMPLATE, rs_template_logger_log }, { ALPROTO_RDP, (EveJsonSimpleTxLogFunc)rs_rdp_to_json }, { ALPROTO_HTTP2, rs_http2_log_json }, diff --git a/src/util-profiling.h b/src/util-profiling.h index 564f62b5c78f..094c6de6d209 100644 --- a/src/util-profiling.h +++ b/src/util-profiling.h @@ -170,13 +170,12 @@ PktProfiling *SCProfilePacketStart(void); (dp)->alproto = (id); \ } -#define PACKET_PROFILING_APP_END(dp, id) \ - if (profiling_packets_enabled) { \ - BUG_ON((id) != (dp)->alproto); \ - (dp)->ticks_end = UtilCpuGetTicks(); \ - if ((dp)->ticks_start != 0 && (dp)->ticks_start < ((dp)->ticks_end)) { \ - (dp)->ticks_spent = ((dp)->ticks_end - (dp)->ticks_start); \ - } \ +#define PACKET_PROFILING_APP_END(dp) \ + if (profiling_packets_enabled) { \ + (dp)->ticks_end = UtilCpuGetTicks(); \ + if ((dp)->ticks_start != 0 && (dp)->ticks_start < ((dp)->ticks_end)) { \ + (dp)->ticks_spent = ((dp)->ticks_end - (dp)->ticks_start); \ + } \ } #define PACKET_PROFILING_APP_PD_START(dp) \ @@ -340,7 +339,7 @@ void SCProfilingDump(void); #define PACKET_PROFILING_RESET(p) #define PACKET_PROFILING_APP_START(dp, id) -#define PACKET_PROFILING_APP_END(dp, id) +#define PACKET_PROFILING_APP_END(d) #define PACKET_PROFILING_APP_RESET(dp) #define PACKET_PROFILING_APP_STORE(dp, p) diff --git a/suricata.yaml.in b/suricata.yaml.in index 89d1516a4668..4fa495b3f05e 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -311,6 +311,8 @@ outputs: - mqtt: # passwords: yes # enable output of passwords - http2 + # dns over http2 + - doh2 - pgsql: enabled: no # passwords: yes # enable output of passwords. Disabled by default @@ -939,6 +941,8 @@ app-layer: ssh: enabled: yes #hassh: yes + doh2: + enabled: yes http2: enabled: yes # Maximum number of live HTTP2 streams in a flow