diff --git a/etc/schema.json b/etc/schema.json index c194017ddf6f..e841d4d915c3 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -3743,6 +3743,9 @@ "dns_udp": { "$ref": "#/$defs/stats_applayer_error" }, + "doh2": { + "$ref": "#/$defs/stats_applayer_error" + }, "enip_tcp": { "$ref": "#/$defs/stats_applayer_error" }, @@ -3851,6 +3854,9 @@ "dns_udp": { "type": "integer" }, + "doh2": { + "type": "integer" + }, "enip_tcp": { "type": "integer" }, @@ -3968,6 +3974,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 97db321e2249..352e279333c0 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/dns/dns.rs b/rust/src/dns/dns.rs index 382c76ae59b5..d08bd74e0a70 100644 --- a/rust/src/dns/dns.rs +++ b/rust/src/dns/dns.rs @@ -250,10 +250,10 @@ impl Transaction for DNSTransaction { impl DNSTransaction { pub fn new(direction: Direction) -> Self { - Self { - tx_data: AppLayerTxData::for_direction(direction), + Self { + tx_data: AppLayerTxData::for_direction(direction), ..Default::default() - } + } } /// Get the DNS transactions ID (not the internal tracking ID). @@ -277,6 +277,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 { @@ -334,6 +339,111 @@ impl State for DNSState { } } +fn dns_validate_header<'a>(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 +} + +#[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_request_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_response_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() @@ -386,60 +496,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_request_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; - } + }, } } @@ -468,56 +549,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_response_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 5212b1a0da7c..dd0030faac02 100644 --- a/rust/src/dns/log.rs +++ b/rust/src/dns/log.rs @@ -605,7 +605,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 { @@ -632,7 +632,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(_) => { @@ -646,7 +646,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 { @@ -659,7 +659,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 1c595a0cb0f8..b2b528fca3b2 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 14d7b47dfb03..f302c7d7be7a 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; @@ -144,6 +148,12 @@ pub struct HTTP2Transaction { pub escaped: Vec>, pub req_line: Vec, pub resp_line: Vec, + + is_doh_response: bool, + // dns response buffer + pub doh_response_buf: Vec, + pub dns_request_tx: Option, + pub dns_response_tx: Option, } impl Transaction for HTTP2Transaction { @@ -175,6 +185,10 @@ impl HTTP2Transaction { escaped: Vec::with_capacity(16), req_line: Vec::new(), resp_line: Vec::new(), + is_doh_response: false, + doh_response_buf: Vec::new(), + dns_request_tx: None, + dns_response_tx: None, } } @@ -184,13 +198,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(); } @@ -202,12 +216,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_response = 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'@') { @@ -229,6 +258,14 @@ impl HTTP2Transaction { } } } + if doh { + 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) { @@ -237,10 +274,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; @@ -256,7 +293,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, &[]) } @@ -299,13 +343,19 @@ impl HTTP2Transaction { &xid, ); }; + // we store DNS response, and process it when complete + if self.is_doh_response && self.doh_response_buf.len() < 0xFFFF { + // a DNS message is U16_MAX + self.doh_response_buf.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 { @@ -315,7 +365,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 @@ -323,13 +373,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; @@ -376,6 +426,7 @@ impl HTTP2Transaction { } _ => {} } + return r; } } @@ -589,7 +640,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(); @@ -650,7 +701,7 @@ impl HTTP2State { } 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 self.transactions.back_mut().unwrap(); } @@ -661,9 +712,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; @@ -970,7 +1019,14 @@ impl HTTP2State { ); let tx = self.find_or_create_tx(&head, &txdata, dir); - tx.handle_frame(&head, &txdata, dir); + if let Some(doh_req_buf) = tx.handle_frame(&head, &txdata, dir) { + if let Ok(dtx) = dns_parse_request(&doh_req_buf) { + tx.dns_request_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange(flow, ALPROTO_DOH2); + } + } + } let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; let ftype = head.ftype; let sid = head.stream_id; @@ -999,16 +1055,38 @@ 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(_) => { + if !tx_same.doh_response_buf.is_empty() && over { + if let Ok(dtx) = + dns_parse_response(&tx_same.doh_response_buf) + { + tx_same.dns_response_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange( + flow, + ALPROTO_DOH2, + ); + } + } + } + } + _ => { + self.set_event(HTTP2Event::FailedDecompression); + } } } } @@ -1103,6 +1181,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); @@ -1231,15 +1325,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() @@ -1251,7 +1350,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, @@ -1310,4 +1409,19 @@ 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); + } + 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..6bfa009f9358 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); @@ -751,6 +753,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/Makefile.am b/src/Makefile.am index 4695c2d35f51..749cd22f3e68 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -429,6 +429,7 @@ noinst_HEADERS = \ output-json-snmp.h \ output-json-ssh.h \ output-json-stats.h \ + output-json-doh2.h \ output-json-template.h \ output-json-tftp.h \ output-json-tls.h \ @@ -1041,6 +1042,7 @@ libsuricata_c_a_SOURCES = \ output-json-snmp.c \ output-json-ssh.c \ output-json-stats.c \ + output-json-doh2.c \ output-json-template.c \ output-json-tftp.c \ output-json-tls.c \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 690950d34e72..c3f44cf1da64 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -1866,6 +1866,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); @@ -2047,6 +2057,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 4ee4bac10ad8..60b05ad29cef 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 368efacd88d7..c08b1db4adb6 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -60,6 +60,7 @@ const AppProtoStringTuple AppProtoStrings[ALPROTO_MAX] = { { ALPROTO_MQTT, "mqtt" }, { ALPROTO_PGSQL, "pgsql" }, { ALPROTO_TELNET, "telnet" }, + { 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 dd372550cbf5..6af2f27cbb1d 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -56,6 +56,7 @@ enum AppProtoEnum { ALPROTO_MQTT, ALPROTO_PGSQL, ALPROTO_TELNET, + ALPROTO_DOH2, ALPROTO_TEMPLATE, ALPROTO_RDP, ALPROTO_HTTP2, @@ -84,10 +85,27 @@ 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) { 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) || (alproto == ALPROTO_HTTP); @@ -97,6 +115,48 @@ static inline bool AppProtoEquals(AppProto sigproto, AppProto alproto) return (sigproto == alproto); } +// 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/detect-dns-opcode.c b/src/detect-dns-opcode.c index 853b01f0097d..eacd717b48ab 100644 --- a/src/detect-dns-opcode.c +++ b/src/detect-dns-opcode.c @@ -66,6 +66,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.c b/src/detect-dns-query.c index 43e1595e491a..2a6b0450219e 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 849930a7a9cf..a618c4b0c141 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -107,6 +107,11 @@ void DetectAppLayerMpmRegister2(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) { + DetectAppLayerMpmRegister2(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 58aee1bfc078..0433842927bc 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -247,6 +247,10 @@ void DetectAppLayerInspectEngineRegister2(const char *name, } else { direction = 1; } + // every DNS or HTTP2 can be accessed from DOH2 + if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { + DetectAppLayerInspectEngineRegister2(name, ALPROTO_DOH2, dir, progress, Callback2, GetData); + } DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); diff --git a/src/detect-parse.c b/src/detect-parse.c index e1ac5f74b5a4..ccb2be844824 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1742,20 +1742,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 5cb4e6bfbc44..a609b4893897 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1102,7 +1102,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-json-doh2.c b/src/output-json-doh2.c new file mode 100644 index 000000000000..23caf7957ae0 --- /dev/null +++ b/src/output-json-doh2.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2023 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 + * + * \author Philippe Antoine + * + * Implement JSON/eve logging app-layer DoH2. + */ + +#include "suricata-common.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-doh2.h" +#include "rust.h" + +typedef struct LogDoH2FileCtx_ { + uint32_t flags; + OutputJsonCtx *eve_ctx; +} LogDoH2FileCtx; + +typedef struct LogDoH2LogThread_ { + LogDoH2FileCtx *doh2log_ctx; + OutputJsonThreadCtx *ctx; +} LogDoH2LogThread; + +static int JsonDoH2Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, + void *tx, uint64_t tx_id) +{ + LogDoH2LogThread *thread = thread_data; + + JsonBuilder *js = + CreateEveHeader(p, LOG_DIR_PACKET, "doh2", NULL, thread->doh2log_ctx->eve_ctx); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + if (!rs_http2_log_json(tx, js)) { + goto error; + } + + OutputJsonBuilderBuffer(js, thread->ctx); + jb_free(js); + + return TM_ECODE_OK; + +error: + jb_free(js); + return TM_ECODE_FAILED; +} + +static void OutputDoH2LogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogDoH2FileCtx *doh2log_ctx = (LogDoH2FileCtx *)output_ctx->data; + SCFree(doh2log_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputDoH2LogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogDoH2FileCtx *doh2log_ctx = SCCalloc(1, sizeof(*doh2log_ctx)); + if (unlikely(doh2log_ctx == NULL)) { + return result; + } + doh2log_ctx->eve_ctx = ajt; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(doh2log_ctx); + return result; + } + output_ctx->data = doh2log_ctx; + output_ctx->DeInit = OutputDoH2LogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DOH2); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonDoH2LogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogDoH2LogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogDoH2. \"initdata\" is NULL."); + goto error_exit; + } + + thread->doh2log_ctx = ((OutputCtx *)initdata)->data; + thread->ctx = CreateEveThreadCtx(t, thread->doh2log_ctx->eve_ctx); + if (!thread->ctx) { + goto error_exit; + } + *data = (void *)thread; + + return TM_ECODE_OK; + +error_exit: + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonDoH2LogThreadDeinit(ThreadVars *t, void *data) +{ + LogDoH2LogThread *thread = (LogDoH2LogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + FreeEveThreadCtx(thread->ctx); + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonDoH2LogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDoH2Log", "eve-log.doh2", + OutputDoH2LogInitSub, ALPROTO_DOH2, JsonDoH2Logger, JsonDoH2LogThreadInit, + JsonDoH2LogThreadDeinit, NULL); +} diff --git a/src/output-json-doh2.h b/src/output-json-doh2.h new file mode 100644 index 000000000000..5847d5034133 --- /dev/null +++ b/src/output-json-doh2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 + * + * \author FirstName LastName + */ + +#ifndef __OUTPUT_JSON_DOH2_H__ +#define __OUTPUT_JSON_DOH2_H__ + +void JsonDoH2LogRegister(void); + +#endif /* __OUTPUT_JSON_DOH2_H__ */ diff --git a/src/output.c b/src/output.c index 149dda58c284..0fb2e28a73fc 100644 --- a/src/output.c +++ b/src/output.c @@ -80,6 +80,7 @@ #include "output-json-rfb.h" #include "output-json-mqtt.h" #include "output-json-pgsql.h" +#include "output-json-doh2.h" #include "output-json-template.h" #include "output-json-rdp.h" #include "output-json-http2.h" @@ -1117,6 +1118,8 @@ void OutputRegisterLoggers(void) JsonMQTTLogRegister(); /* Pgsql JSON logger. */ JsonPgsqlLogRegister(); + /* DoH2 JSON logger. */ + JsonDoH2LogRegister(); /* Template JSON logger. */ JsonTemplateLogRegister(); /* RDP JSON logger. */ @@ -1157,8 +1160,9 @@ static EveJsonSimpleAppLayerLogger simple_json_applayer_loggers[ALPROTO_MAX] = { { ALPROTO_SIP, (EveJsonSimpleTxLogFunc)rs_sip_log_json }, { ALPROTO_RFB, rs_rfb_logger_log }, { ALPROTO_MQTT, JsonMQTTAddMetadata }, - { ALPROTO_PGSQL, NULL }, // TODO missing - { ALPROTO_TELNET, NULL }, // no logging + { ALPROTO_PGSQL, NULL }, // TODO missing + { ALPROTO_TELNET, NULL }, // no logging + { 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/suricata.yaml.in b/suricata.yaml.in index 630399126dbe..ab41f327a00b 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -303,6 +303,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 @@ -928,6 +930,8 @@ app-layer: ssh: enabled: yes #hassh: yes + doh2: + enabled: yes http2: enabled: yes # Maximum number of live HTTP2 streams in a flow