-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Dns over http2 5773 v1 #9965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dns over http2 5773 v1 #9965
Changes from all commits
0c908a4
db356b1
08f4f0a
1f79632
746cf64
2c350df
e7c66e3
f911442
123de0a
4ca50c9
083cc8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,9 @@ use crate::conf::conf_get; | |
| use crate::core::*; | ||
| use crate::filecontainer::*; | ||
| use crate::filetracker::*; | ||
|
|
||
| use crate::dns::dns::rs_dns_state_new; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO use |
||
|
|
||
| use nom7::Err; | ||
| use std; | ||
| use std::collections::VecDeque; | ||
|
|
@@ -144,6 +147,8 @@ pub struct HTTP2Transaction { | |
| pub escaped: Vec<Vec<u8>>, | ||
| pub req_line: Vec<u8>, | ||
| pub resp_line: Vec<u8>, | ||
|
|
||
| pub doh: Vec<u8>, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: add comment to explain this field
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like it needs a longer name, I suppose a comment would help. |
||
| } | ||
|
|
||
| impl Transaction for HTTP2Transaction { | ||
|
|
@@ -175,6 +180,7 @@ impl HTTP2Transaction { | |
| escaped: Vec::with_capacity(16), | ||
| req_line: Vec::new(), | ||
| resp_line: Vec::new(), | ||
| doh: Vec::new(), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -202,12 +208,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<Vec<u8>> { | ||
| 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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Victor, is this ok to match on the different header names ? |
||
| if block.value == b"application/dns-message" { | ||
| doh = true; | ||
| } | ||
| } else if block.name == b"content-type" { | ||
| if block.value == b"application/dns-message" { | ||
| // push original 2-bytes DNS/TCP header | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: have a longer comment |
||
| self.doh.push(0); | ||
| self.doh.push(0); | ||
| } | ||
| } 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 +250,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 +266,9 @@ 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<u8>, 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; | ||
|
|
@@ -299,13 +327,17 @@ impl HTTP2Transaction { | |
| &xid, | ||
| ); | ||
| }; | ||
| if !self.doh.is_empty() { | ||
| self.doh.extend_from_slice(decompressed); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO comment |
||
| } | ||
| return Ok(()); | ||
| } | ||
|
|
||
| fn handle_frame( | ||
| &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction, | ||
| ) { | ||
| ) -> Option<Vec<u8>> { | ||
| //handle child_stream_id changes | ||
| let mut r = None; | ||
| match data { | ||
| HTTP2FrameTypeData::PUSHPROMISE(hs) => { | ||
| if dir == Direction::ToClient { | ||
|
|
@@ -315,21 +347,21 @@ 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 | ||
| && header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0 | ||
| { | ||
| 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 +408,7 @@ impl HTTP2Transaction { | |
| } | ||
| _ => {} | ||
| } | ||
| return r; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -441,6 +474,9 @@ pub struct HTTP2State { | |
| dynamic_headers_tc: HTTP2DynTable, | ||
| transactions: VecDeque<HTTP2Transaction>, | ||
| progress: HTTP2ConnectionState, | ||
| // layered packets contents for DNS over HTTP2 | ||
| layered: Vec<Vec<u8>>, | ||
| dns_state: Option<*mut std::os::raw::c_void>, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO comment I am not sure if this should be generic or DNS-specific...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is DoH a little special in that it doesn't follow the normal PROTO-over-HTTP formats like something like GRPC might do? Where they just use the bodies for their payload?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Indeed DoH is not exactly like that : it has DNS message in the URI for requests (a base64-encoded parameter), and in bodies for responses. Why does it matter ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Say we were to support gRPC over HTTP, and Avro over HTTP, and Thrift over HTTP. Those would also make sense to have something generic. As DoH is different, could it be put within the same generic container or does it always need some special handling due to be in the URI. I guess not. But that's the path of thought I was on.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed I was hesitating for naming
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm OK without over thinking making it more generic until we have the need to make it more generic. |
||
| } | ||
|
|
||
| impl State<HTTP2Transaction> for HTTP2State { | ||
|
|
@@ -473,6 +509,8 @@ impl HTTP2State { | |
| dynamic_headers_tc: HTTP2DynTable::new(), | ||
| transactions: VecDeque::new(), | ||
| progress: HTTP2ConnectionState::Http2StateInit, | ||
| layered: Vec::new(), | ||
| dns_state: None, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -925,6 +963,7 @@ impl HTTP2State { | |
|
|
||
| fn parse_frames( | ||
| &mut self, mut input: &[u8], il: usize, dir: Direction, flow: *const Flow, | ||
| pstate: *mut std::os::raw::c_void | ||
| ) -> AppLayerResult { | ||
| while !input.is_empty() { | ||
| match parser::http2_parse_frame_header(input) { | ||
|
|
@@ -970,7 +1009,7 @@ impl HTTP2State { | |
| ); | ||
|
|
||
| let tx = self.find_or_create_tx(&head, &txdata, dir); | ||
| tx.handle_frame(&head, &txdata, dir); | ||
| let odoh = tx.handle_frame(&head, &txdata, dir); | ||
| let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; | ||
| let ftype = head.ftype; | ||
| let sid = head.stream_id; | ||
|
|
@@ -1002,19 +1041,58 @@ impl HTTP2State { | |
| 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(_) => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO helper function for less indent |
||
| if !tx_same.doh.is_empty() { | ||
| if over { | ||
| // fix DNS/TCP header length field | ||
| tx_same.doh[0] = ((tx_same.doh.len() - 2) >> 8) as u8; | ||
| tx_same.doh[1] = ((tx_same.doh.len() - 2) & 0xFF) as u8; | ||
| self.layered.push(tx_same.doh.to_vec()); | ||
| if self.dns_state.is_none() { | ||
| unsafe { | ||
| self.dns_state = Some(rs_dns_state_new(std::ptr::null_mut(), ALPROTO_HTTP2)); | ||
| } | ||
| } | ||
| unsafe { | ||
| AppLayerParserStateSetFlag( | ||
| pstate, | ||
| APP_LAYER_PARSER_LAYERED_PACKET, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| _ => { | ||
| self.set_event(HTTP2Event::FailedDecompression); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| None => panic!("no SURICATA_HTTP2_FILE_CONFIG"), | ||
| } | ||
| } | ||
| if let Some(doh) = odoh { | ||
| self.layered.push(doh); | ||
| unsafe { | ||
| AppLayerParserStateSetFlag( | ||
| pstate, | ||
| APP_LAYER_PARSER_LAYERED_PACKET, | ||
| ); | ||
| } | ||
| if self.dns_state.is_none() { | ||
| unsafe { | ||
| self.dns_state = Some(rs_dns_state_new(std::ptr::null_mut(), ALPROTO_HTTP2)); | ||
| } | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| input = &rem[hlsafe..]; | ||
| } | ||
| Err(Err::Incomplete(_)) => { | ||
|
|
@@ -1033,7 +1111,7 @@ impl HTTP2State { | |
| return AppLayerResult::ok(); | ||
| } | ||
|
|
||
| fn parse_ts(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult { | ||
| fn parse_ts(&mut self, mut input: &[u8], flow: *const Flow, pstate: *mut std::os::raw::c_void) -> AppLayerResult { | ||
| //very first : skip magic | ||
| let mut magic_consumed = 0; | ||
| if self.progress < HTTP2ConnectionState::Http2StateMagicDone { | ||
|
|
@@ -1073,7 +1151,7 @@ impl HTTP2State { | |
| } | ||
|
|
||
| //then parse all we can | ||
| let r = self.parse_frames(input, il, Direction::ToServer, flow); | ||
| let r = self.parse_frames(input, il, Direction::ToServer, flow, pstate); | ||
| if r.status == 1 { | ||
| //adds bytes consumed by banner to incomplete result | ||
| return AppLayerResult::incomplete(r.consumed + magic_consumed as u32, r.needed); | ||
|
|
@@ -1082,7 +1160,7 @@ impl HTTP2State { | |
| } | ||
| } | ||
|
|
||
| fn parse_tc(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult { | ||
| fn parse_tc(&mut self, mut input: &[u8], flow: *const Flow, pstate: *mut std::os::raw::c_void) -> AppLayerResult { | ||
| //first consume frame bytes | ||
| let il = input.len(); | ||
| if self.response_frame_size > 0 { | ||
|
|
@@ -1097,10 +1175,43 @@ impl HTTP2State { | |
| } | ||
| } | ||
| //then parse all we can | ||
| return self.parse_frames(input, il, Direction::ToClient, flow); | ||
| return self.parse_frames(input, il, Direction::ToClient, flow, pstate); | ||
| } | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCHttp2ClearLayered( | ||
| s: &mut HTTP2State | ||
| ) { | ||
| s.layered.clear(); | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCHttp2GetLayeredState( | ||
| s: &HTTP2State | ||
| ) -> *mut std::os::raw::c_void { | ||
| match s.dns_state { | ||
| Some(d) => d, | ||
| _ => std::ptr::null_mut(), | ||
| } | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCHttp2GetLayered( | ||
| s: &HTTP2State, buffer: *mut *const u8, buffer_len: *mut u32, i: u32, | ||
| ) -> bool { | ||
| let i = i as usize; | ||
| if i < s.layered.len() { | ||
| *buffer = s.layered[i].as_ptr(); | ||
| *buffer_len = s.layered[i].len() as u32; | ||
| return true; | ||
| } | ||
|
|
||
| *buffer = std::ptr::null(); | ||
| *buffer_len = 0; | ||
| return false; | ||
| } | ||
|
|
||
| // C exports. | ||
|
|
||
| export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction); | ||
|
|
@@ -1175,22 +1286,22 @@ pub unsafe extern "C" fn rs_http2_state_tx_free(state: *mut std::os::raw::c_void | |
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn rs_http2_parse_ts( | ||
| flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, | ||
| flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, | ||
| stream_slice: StreamSlice, _data: *const std::os::raw::c_void, | ||
| ) -> AppLayerResult { | ||
| let state = cast_pointer!(state, HTTP2State); | ||
| let buf = stream_slice.as_slice(); | ||
| return state.parse_ts(buf, flow); | ||
| return state.parse_ts(buf, flow, pstate); | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn rs_http2_parse_tc( | ||
| flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, | ||
| flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, | ||
| stream_slice: StreamSlice, _data: *const std::os::raw::c_void, | ||
| ) -> AppLayerResult { | ||
| let state = cast_pointer!(state, HTTP2State); | ||
| let buf = stream_slice.as_slice(); | ||
| return state.parse_tc(buf, flow); | ||
| return state.parse_tc(buf, flow, pstate); | ||
| } | ||
|
|
||
| #[no_mangle] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please suggest me a better name :-p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stacked? Encapped? Encapsulated? They'll kinda mean the same thing.