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
Dns over http2 5773 v1 #9965
Conversation
Ticket: 5773
@@ -477,6 +477,7 @@ pub const APP_LAYER_PARSER_EOF_TS : u16 = BIT_U16!(5); | |||
pub const APP_LAYER_PARSER_EOF_TC : u16 = BIT_U16!(6); | |||
pub const APP_LAYER_PARSER_TRUNC_TS : u16 = BIT_U16!(7); | |||
pub const APP_LAYER_PARSER_TRUNC_TC : u16 = BIT_U16!(8); | |||
pub const APP_LAYER_PARSER_LAYERED_PACKET : u16 = BIT_U16!(11); |
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.
@@ -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; |
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.
TODO use rs_dns_state_free
@@ -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>, |
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.
TODO: add comment to explain this field
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.
I feel like it needs a longer name, I suppose a comment would help.
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 |
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.
Victor, is this ok to match on the different header names ?
} | ||
} else if block.name == b"content-type" { | ||
if block.value == b"application/dns-message" { | ||
// push original 2-bytes DNS/TCP header |
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.
TODO: have a longer comment
@@ -299,13 +327,17 @@ impl HTTP2Transaction { | |||
&xid, | |||
); | |||
}; | |||
if !self.doh.is_empty() { | |||
self.doh.extend_from_slice(decompressed); |
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.
TODO comment
@@ -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>, |
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.
TODO comment
I am not sure if this should be generic or DNS-specific...
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.
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?
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.
Where they just use the bodies for their payload?
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 ?
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.
Where they just use the bodies for their payload?
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 ?
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.
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.
Indeed I was hesitating for naming dns_state
or something more generic...
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.
I'm OK without over thinking making it more generic until we have the need to make it more generic.
flow).is_err() { | ||
self.set_event(HTTP2Event::FailedDecompression); | ||
flow) { | ||
Ok(_) => { |
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.
TODO helper function for less indent
unsafe { | ||
self.dns_state = Some(rs_dns_state_new(std::ptr::null_mut(), ALPROTO_HTTP2)); | ||
} | ||
} |
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.
we
- set a flag in pstate (so that AppLayerParserParse knows that it should deque packets)
- push the to-be pseudo-packet payload in HTTP2 state
- create a DNS state if there is not one yet
@@ -1438,6 +1441,32 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow | |||
} | |||
} | |||
|
|||
if (pstate->flags & APP_LAYER_PARSER_LAYERED_PACKET) { | |||
// hardcoded logic |
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.
TODO: instead of hardcoded logic, protocols (such as HTTP2) should have a generic function that returns the pseudo-packets payloads
continue; | ||
} | ||
PKT_SET_SRC(np, PKT_SRC_APP_LAYER_LAYERED); | ||
// TODO np->flags |= PKT_PSEUDO_DETECTLOG_FLUSH; |
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.
TODO: remove until we need special flagging
np->payload = SCMalloc(b_len); | ||
memcpy(np->payload, b, b_len); | ||
np->payload_len = b_len; | ||
PacketEnqueueNoLock(&tv->decode_pq, np); |
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.
@victorjulien is &tv->decode_pq
the right place to ensue such pseudo packets ?
I fear this will not work if I encapsulate my DNS over HTTP2 on GRE or other tunnels...
(&fw->pq
is not already accessible here)
// switch flow to new protocol | ||
void *alstate_orig = p->flow->alstate; | ||
AppProto alproto_orig = p->flow->alproto; | ||
// hardcoded logic |
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.
TODO: have a not hardcoded logic for this
SCFree(x->payload); | ||
FlowDeReference(&x->flow); | ||
TmqhOutputPacketpool(tv, x); | ||
} |
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.
We deque and process the packets, switching the protocol and state...
FramesPrune(x->flow, x); | ||
|
||
// switch back to real protocol | ||
p->flow->alstate = alstate_orig; |
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.
Is flow locked ? Can a race condition happen between changing alstate
and alproto
?
// TODO np->flags |= PKT_PSEUDO_DETECTLOG_FLUSH; | ||
np->payload = SCMalloc(b_len); | ||
memcpy(np->payload, b, b_len); | ||
np->payload_len = b_len; |
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.
Should I put a timestamp for this packet ? Where can I get it ?
@@ -205,5 +205,8 @@ uint64_t StreamDataRightEdge(const TcpStream *stream, const bool eof); | |||
void StreamTcpThreadCacheEnable(void); | |||
void StreamTcpThreadCacheCleanup(void); | |||
|
|||
// move elsewhere |
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.
Ideas where this function should belong ?
return np; | ||
error: | ||
FlowDeReference(&np->flow); | ||
PacketPoolReturnPacket(np); |
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.
This PacketPoolReturnPacket
was missing originally, correct ?
FLOWWORKER_PROFILING_END(x, PROFILE_FLOWWORKER_DETECT); | ||
} | ||
OutputLoggerLog(tv, x, fw->output_thread); | ||
FramesPrune(x->flow, x); |
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.
I should test frames I guess
Before diving into the details I would like to read what the high level design is around the "layered" logic and pseudo packet(s), and how it would hook into detect and logging. |
I wrote this :
What do you expect different for a high-level design ? |
There is no change to the json schema.
|
An actual explanation of the flow of packets and logic? Just a few half sentences isn't an explanation. |
This PR is a POC for DNS over HTTP2. As such, it is an example of a flow having multiple app-layer protocols. (DCERPC over SMB is the example in the current code).
Functionnaly, in terms of output : The goal, in terms of output, is to keep the same
The current implementation also adds one bit flag for the u16 app-layer-parser flags (like
Which constraints ?
For time, I would roughly expect that a DoH packet takes twice as much time as a HTTP2 or DNS packet as we parse/log/detect both twice. For space, we use a bit more memory for HTTP2 flows (not for other flows) and transactions, adding one or two fields that are pointers, unused if it is not DoH, and that are roughly twice the size of a HTTP2 flow when it is DoH (as it is both a DNS and a HTTP2 flow) This is for regular processing. |
Information: QA ran without warnings. Pipeline 16892 |
Thanks Philippe, this is a very helpful explanation. Lets discuss the overall design before doing more code work on the current PR, as I'm not happy with the double packet logic. I would like the engine itself to understand layered protocols, instead of working around the current lack of support for this use case. Some questions on what the design should support: Conceptually, would we want to offer the user a way to match on both http and dns at the same time? e.g.
Would we want EVE to log both in a single record? e.g. {
"event_type":"doh"
"dns": {
}
"http2": {
}
} |
In my thinking we could do 2 models:
|
Looks nice as it brings more features. This would be possible with the ALPROTO_DOH solution This looks kind of a specific solution for DoH, does not seem so generic for layered protocols. I mean will there be a combinatorial explosion for ALPROTO with such a solution ? Or not because there are not so many existing combinations
Are not DNS transactions unidirectional (and HTTP2 are bidirectional) ? |
SMB/DCERPC seem quite similar. I think there the (hardcoded) solution is closer to having 2 states and have the detect/log etc code iterate them where needed.
Yeah, so perhaps in that case you'd need tx(http2)->tx(dns)[2] or something, where the direction determines which dns tx to use |
So, I will try the |
The more I thought about this, the more I liked this idea. As we start seeing some DOH Providers offering "specialized" dns services and and being used to facilitate Alternated DNS Roots, the ability to match the :authority/host header with the DNS query/domain could be used to provide high fidelity detection. So, given a single query for DOH, the following signatures would fire.
sid:1 allows us to write typical DNS formatted rules, where I don't care what protocol is used to make the query Question:
|
Note : The |
I do not see any difficulties for this
doh means to use all dns buffers |
Correct. |
New version in #10040 |
Link to redmine ticket:
https://redmine.openinfosecfoundation.org/issues/5773
Describe changes:
OISF/suricata-verify#1509
Draft to get feedback about approach...
Leaving comments on the code for specific questions
TODO :
Generic approach :