Skip to main content

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