]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
doh: implement dns over http2 app-proto
authorPhilippe Antoine <pantoine@oisf.net>
Tue, 12 Dec 2023 13:58:02 +0000 (14:58 +0100)
committerVictor Julien <victor@inliniac.net>
Sat, 20 Jul 2024 08:37:58 +0000 (10:37 +0200)
Ticket: 5773

etc/schema.json
rust/src/applayer.rs
rust/src/http2/http2.rs
src/app-layer-detect-proto.c
src/app-layer-detect-proto.h
src/app-layer-protos.c
src/app-layer-protos.h
src/output.c
suricata.yaml.in

index cffb15afd35742b900367d27afe0b5325c77ad85..89329b094431acba72241c4cb07ffa99c5967bb3 100644 (file)
                                     "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"
                                     "description": "Number of flows for DNS/UDP protocol",
                                     "type": "integer"
                                 },
+                                "doh2": {
+                                    "type": "integer"
+                                },
                                 "enip_tcp": {
                                     "description": "Number of flows for ENIP/TCP",
                                     "type": "integer"
                                     "description": "Number of transactions for DNS/UDP protocol",
                                     "type": "integer"
                                 },
+                                "doh2": {
+                                    "type": "integer"
+                                },
                                 "enip_tcp": {
                                     "description": "Number of transactions for ENIP/TCP",
                                     "type": "integer"
index 187245917ab63351248d12aed332c4d1e2054ee0..fb45b9af4168f072ea353d791954fd96d8dc18c6 100644 (file)
@@ -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);
index bd3eccc56c90227c29721d6b3ef5dbf63254fc42..063f80a15d8cb53a7fbd4e5545267f577fa3893c 100644 (file)
@@ -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.");
+    }
 }
index 5748587f74bbd4bf238e63481379ca82e6ff05df..298392313e3183fc42fcfb379a1a04b92b818192 100644 (file)
@@ -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);
index 158ad234dd4955452ab0f9794b17009116b476d5..adc458ed93f2f6b19c914db6db418b4641261362 100644 (file)
@@ -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.
  */
index a13e3e8d5d4f59a24d86c1d726edc81a7a56f94e..e82d8688f557101fa562973e786c5df935b42761 100644 (file)
@@ -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" },
index b14f475d8534747fb8edafda30d7a29c33103d07..85c69b225e180b3da4a0c56fe34e7f027f706b91 100644 (file)
@@ -58,6 +58,7 @@ enum AppProtoEnum {
     ALPROTO_TELNET,
     ALPROTO_WEBSOCKET,
     ALPROTO_LDAP,
+    ALPROTO_DOH2,
     ALPROTO_TEMPLATE,
     ALPROTO_RDP,
     ALPROTO_HTTP2,
index 49b2c84ebffb4eb2eb33a1a21a117b728de4ac7c..f621964146544afd05c00528c2604593eb2b58c1 100644 (file)
@@ -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 },
index e753f42e6d26a9741f4e83f7a8de386fdfad3116..fae02ce7ac08ad2f0532313eb2a8a0d205c97ed9 100644 (file)
@@ -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