Skip to content
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 v12 #11292

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 85 additions & 17 deletions rust/src/http2/http2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,7 +64,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

Expand Down Expand Up @@ -146,6 +149,12 @@ pub struct HTTP2Transaction {
pub escaped: Vec<Vec<u8>>,
pub req_line: Vec<u8>,
pub resp_line: Vec<u8>,

is_doh_response: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this all be wrapped in an Option? It seems like a large expansion of the http/2 tx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a good idea, will try to improve on it

// dns response buffer
pub doh_response_buf: Vec<u8>,
pub dns_request_tx: Option<DNSTransaction>,
pub dns_response_tx: Option<DNSTransaction>,
}

impl Transaction for HTTP2Transaction {
Expand Down Expand Up @@ -177,6 +186,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,
}
}

Expand Down Expand Up @@ -204,12 +217,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.as_ref() == b"content-encoding" {
self.decoder.http2_encoding_fromvec(&block.value, dir);
} else if block.name.as_ref() == b"accept" {
//TODO? faster pattern matching
if block.value.as_ref() == b"application/dns-message" {
doh = true;
}
} else if block.name.as_ref() == b"content-type" {
if block.value.as_ref() == b"application/dns-message" {
self.is_doh_response = true;
}
} else if block.name.as_ref() == 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'@') {
Expand All @@ -231,6 +259,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) {
Expand All @@ -239,11 +275,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<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;
Expand Down Expand Up @@ -309,13 +344,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<Vec<u8>> {
//handle child_stream_id changes
let mut r = None;
match data {
HTTP2FrameTypeData::PUSHPROMISE(hs) => {
if dir == Direction::ToClient {
Expand All @@ -325,21 +366,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;
Expand Down Expand Up @@ -386,6 +427,7 @@ impl HTTP2Transaction {
}
_ => {}
}
return r;
}
}

Expand Down Expand Up @@ -897,13 +939,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
Expand Down Expand Up @@ -1062,7 +1106,8 @@ impl HTTP2State {
}
let tx = tx.unwrap();
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;
Expand Down Expand Up @@ -1096,8 +1141,31 @@ impl HTTP2State {
if padded && !rem.is_empty() && usize::from(rem[0]) < hlsafe {
dinput = &rem[1..hlsafe - usize::from(rem[0])];
}
if tx_same.decompress(dinput, dir, sfcm, over, flow).is_err() {
self.set_event(HTTP2Event::FailedDecompression);
let mut output = Vec::with_capacity(
decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE,
);
match tx_same.decompress(
dinput,
&mut output,
dir,
sfcm,
over,
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);
}
}
}
}
_ => {
self.set_event(HTTP2Event::FailedDecompression);
}
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions rust/src/http2/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction};
use super::parser;
use crate::dns::log::{SCDnsLogAnswerEnabled, SCDnsLogJsonAnswer, SCDnsLogJsonQuery};
use crate::jsonbuilder::{JsonBuilder, JsonError};
use std;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -281,6 +282,35 @@ fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result<bool, JsonEr
js.close()?; // http2
js.close()?; // http

if tx.dns_request_tx.is_some() || tx.dns_response_tx.is_some() {
js.open_object("dns")?;
if let Some(dtx) = &tx.dns_request_tx {
let mark = js.get_mark();
let mut has_dns_query = false;
js.open_array("query")?;
for i in 0..0xFFFF {
let mut jsa = JsonBuilder::try_new_object()?;
if !SCDnsLogJsonQuery(dtx, i, 0xFFFFFFFFFFFFFFFF, &mut jsa) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these 0xFFFFFFFFFFFFFFFF uses, looks not very nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed not very nice, this PR is still a draft waiting for the dns log overhaul in https://redmine.openinfosecfoundation.org/issues/6281 to do the same thing...

break;
}
jsa.close()?;
js.append_object(&jsa)?;
has_dns_query = true;
}
if has_dns_query {
js.close()?; // query
} else {
js.restore_mark(&mark)?;
}
}
if let Some(dtx) = &tx.dns_response_tx {
if SCDnsLogAnswerEnabled(dtx, 0xFFFFFFFFFFFFFFFF) {
// logging at root of dns object
SCDnsLogJsonAnswer(dtx, 0xFFFFFFFFFFFFFFFF, js);
}
}
js.close()?; // dns
}
return Ok(has_request || has_response || has_headers);
}

Expand Down
14 changes: 14 additions & 0 deletions rust/src/http2/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::detect::uint::{detect_parse_uint, DetectUintData};
use crate::http2::http2::{HTTP2DynTable, HTTP2_MAX_TABLESIZE};
use nom7::bits::streaming::take as take_bits;
use nom7::branch::alt;
use nom7::bytes::complete::tag;
use nom7::bytes::streaming::{is_a, is_not, take, take_while};
use nom7::combinator::{complete, cond, map_opt, opt, rest, verify};
use nom7::error::{make_error, ErrorKind};
Expand Down Expand Up @@ -759,6 +760,19 @@ pub fn http2_parse_frame_settings(i: &[u8]) -> IResult<&[u8], Vec<HTTP2FrameSett
many0(complete(http2_parse_frame_setting))(i)
}

pub fn doh_extract_request(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
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 {

Expand Down