diff --git a/etc/schema.json b/etc/schema.json index cffb15afd3..89329b0944 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -4591,6 +4591,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" @@ -4753,6 +4756,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" @@ -4922,6 +4928,9 @@ "description": "Number of transactions for DNS/UDP protocol", "type": "integer" }, + "doh2": { + "type": "integer" + }, "enip_tcp": { "description": "Number of transactions for ENIP/TCP", "type": "integer" diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index 187245917a..fb45b9af41 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -471,6 +471,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/http2/http2.rs b/rust/src/http2/http2.rs index bd3eccc56c..063f80a15d 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -36,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; @@ -259,7 +260,7 @@ impl HTTP2Transaction { } } } - if doh { + 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); @@ -344,10 +345,12 @@ 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); + if unsafe {ALPROTO_DOH2} != ALPROTO_UNKNOWN { + // 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(()); } @@ -1105,6 +1108,15 @@ 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); @@ -1153,12 +1165,17 @@ impl HTTP2State { flow, ) { Ok(_) => { - if !tx_same.doh_response_buf.is_empty() { - if over { - if let Ok(dtx) = dns_parse_response( - &tx_same.doh_response_buf, - ) { - tx_same.dns_response_tx = Some(dtx); + if !tx_same.doh_response_buf.is_empty() && over { + if let Ok(mut dtx) = + dns_parse_response(&tx_same.doh_response_buf) + { + dtx.id = 1; + tx_same.dns_response_tx = Some(dtx); + unsafe { + AppLayerForceProtocolChange( + flow, + ALPROTO_DOH2, + ); } } } @@ -1413,7 +1430,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, @@ -1479,4 +1496,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/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 5748587f74..298392313e 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); diff --git a/src/app-layer-detect-proto.h b/src/app-layer-detect-proto.h index 158ad234dd..adc458ed93 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 a13e3e8d5d..e82d8688f5 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -62,6 +62,7 @@ const AppProtoStringTuple AppProtoStrings[ALPROTO_MAX] = { { ALPROTO_TELNET, "telnet" }, { ALPROTO_WEBSOCKET, "websocket" }, { ALPROTO_LDAP, "ldap" }, + { 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 b14f475d85..85c69b225e 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -58,6 +58,7 @@ enum AppProtoEnum { ALPROTO_TELNET, ALPROTO_WEBSOCKET, ALPROTO_LDAP, + ALPROTO_DOH2, ALPROTO_TEMPLATE, ALPROTO_RDP, ALPROTO_HTTP2, diff --git a/src/output.c b/src/output.c index 49b2c84ebf..f621964146 100644 --- a/src/output.c +++ b/src/output.c @@ -1096,6 +1096,10 @@ void OutputRegisterLoggers(void) OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonLdapLog", "eve-log.ldap", OutputJsonLogInitSub, ALPROTO_LDAP, 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, @@ -1152,6 +1156,7 @@ static EveJsonSimpleAppLayerLogger simple_json_applayer_loggers[ALPROTO_MAX] = { { ALPROTO_TELNET, NULL }, // no logging { ALPROTO_WEBSOCKET, rs_websocket_logger_log }, { ALPROTO_LDAP, rs_ldap_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/suricata.yaml.in b/suricata.yaml.in index e753f42e6d..fae02ce7ac 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -321,6 +321,8 @@ outputs: # the maximum parsed message size (see # app-layer configuration) - http2 + # dns over http2 + - doh2 - pgsql: enabled: no # passwords: yes # enable output of passwords. Disabled by default @@ -948,6 +950,8 @@ app-layer: ssh: enabled: yes #hassh: yes + doh2: + enabled: yes http2: enabled: yes # Maximum number of live HTTP2 streams in a flow