Skip to main content

iris_core/protocols/stream/dns/
parser.rs

1// Borrowed from https://github.com/rusticata/rusticata/blob/master/src/dns_udp.rs
2//! DNS transaction parser.
3//!
4//! The DNS transaction parser uses a [fork](https://github.com/thegwan/dns-parser) of the
5//! [dns-parser](https://docs.rs/dns-parser/latest/dns_parser/) crate to parse DNS queries and
6//! responses. It maintains state for tracking outstanding queries and linking query/response pairs.
7//!
8//! Adapted from [the Rusticata DNS
9//! parser](https://github.com/rusticata/rusticata/blob/master/src/dns_udp.rs).
10
11use super::transaction::{DnsQuery, DnsResponse};
12use super::Dns;
13use crate::conntrack::pdu::L4Pdu;
14use crate::protocols::stream::{
15    ConnParsable, ParseResult, ParsingState, ProbeResult, Session, SessionData,
16};
17
18use std::collections::HashMap;
19
20#[derive(Default, Debug)]
21pub struct DnsParser {
22    /// Maps session ID to DNS transaction
23    sessions: HashMap<usize, Dns>,
24    /// Total sessions ever seen (Running session ID)
25    cnt: usize,
26}
27
28impl ConnParsable for DnsParser {
29    fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
30        let offset = pdu.offset();
31        let length = pdu.length();
32        if length == 0 {
33            return ParseResult::Skipped;
34        }
35
36        if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) {
37            self.process(data)
38        } else {
39            log::warn!("Malformed packet");
40            ParseResult::Skipped
41        }
42    }
43
44    fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
45        let dst_port = pdu.ctxt.dst.port();
46        let src_port = pdu.ctxt.src.port();
47        if src_port == 137 || dst_port == 137 {
48            // NetBIOS NBSS looks like DNS, but parser will fail on labels
49            return ProbeResult::NotForUs;
50        }
51        let offset = pdu.offset();
52        let length = pdu.length();
53        if pdu.length() == 0 {
54            return ProbeResult::Unsure;
55        }
56
57        if let Ok(data) = (pdu.mbuf).get_data_slice(offset, length) {
58            match dns_parser::Packet::parse(data) {
59                Ok(packet) => {
60                    if packet.header.query {
61                        if packet.questions.is_empty() {
62                            return ProbeResult::NotForUs;
63                        }
64                    } else if packet.answers.is_empty() {
65                        return ProbeResult::NotForUs;
66                    }
67                    ProbeResult::Certain
68                }
69                _ => ProbeResult::NotForUs,
70            }
71        } else {
72            log::warn!("Malformed packet");
73            ProbeResult::Error
74        }
75    }
76
77    fn remove_session(&mut self, session_id: usize) -> Option<Session> {
78        self.sessions.remove(&session_id).map(|dns| Session {
79            data: SessionData::Dns(Box::new(dns)),
80            id: session_id,
81        })
82    }
83
84    fn drain_sessions(&mut self) -> Vec<Session> {
85        self.sessions
86            .drain()
87            .map(|(session_id, dns)| Session {
88                data: SessionData::Dns(Box::new(dns)),
89                id: session_id,
90            })
91            .collect()
92    }
93
94    fn session_parsed_state(&self) -> ParsingState {
95        ParsingState::Parsing
96    }
97
98    /// We consider DNS to not have a "body"
99    fn body_offset(&mut self) -> Option<usize> {
100        None
101    }
102}
103
104impl DnsParser {
105    pub(crate) fn process(&mut self, data: &[u8]) -> ParseResult {
106        match dns_parser::Packet::parse(data) {
107            Ok(pkt) => {
108                if pkt.header.query {
109                    log::debug!("DNS query");
110                    let query = DnsQuery::parse_query(&pkt);
111                    let query_id = pkt.header.id;
112                    for (session_id, dns) in self.sessions.iter_mut() {
113                        if query_id == dns.transaction_id {
114                            if dns.response.is_some() {
115                                dns.query = Some(query);
116                                return ParseResult::Done(*session_id);
117                            }
118                            break;
119                        }
120                    }
121                    let dns = Dns {
122                        transaction_id: query_id,
123                        query: Some(query),
124                        response: None,
125                    };
126                    let session_id = self.cnt;
127                    self.cnt += 1;
128                    self.sessions.insert(session_id, dns);
129                    ParseResult::Continue(session_id)
130                } else {
131                    log::debug!("DNS answer");
132                    let response = DnsResponse::parse_response(&pkt);
133                    let answer_id = pkt.header.id;
134                    for (session_id, dns) in self.sessions.iter_mut() {
135                        if answer_id == dns.transaction_id {
136                            if dns.query.is_some() {
137                                dns.response = Some(response);
138                                return ParseResult::Done(*session_id);
139                            }
140                            break;
141                        }
142                    }
143                    let dns = Dns {
144                        transaction_id: answer_id,
145                        query: None,
146                        response: Some(response),
147                    };
148                    let session_id = self.cnt;
149                    self.cnt += 1;
150                    self.sessions.insert(session_id, dns);
151                    ParseResult::Continue(session_id)
152                }
153            }
154            e => {
155                log::debug!("parse error: {:?}", e);
156                ParseResult::Skipped
157            }
158        }
159    }
160}