Skip to main content

pharos_server/
admission.rs

1use pharos_proto::{Class, Message, MessageBuilder, Opcode, Rcode};
2
3/// Query admission result. `Respond` writes to the supplied Response buffer.
4pub enum Admission<'a> {
5    Query(Message<'a>),
6    Respond,
7    Drop,
8}
9
10/// Apply the DNS Listener's header, class, and EDNS admission policy.
11pub fn admit<'a>(wire: &'a [u8], allow_non_in: bool, response: &mut Vec<u8>) -> Admission<'a> {
12    // RFC 1035 §4.1.1: the DNS header consists of six 16-bit fields.
13    if wire.len() < 12 {
14        return Admission::Drop;
15    }
16    let flags = u16::from_be_bytes([wire[2], wire[3]]);
17    // RFC 1035 §4.1.1: QR distinguishes responses from queries.
18    if flags & 0x8000 != 0 {
19        return Admission::Drop;
20    }
21    let opcode = (flags >> 11) & 15;
22    if opcode != 0 && opcode != 4 {
23        header_error(wire, response, Rcode::NOTIMP, true);
24        return Admission::Respond;
25    }
26    let count = |i| u16::from_be_bytes([wire[i], wire[i + 1]]);
27    // RFC 9619 §4: a QUERY with more than one question requires FORMERR.
28    if count(4) != 1 || count(6) > 1 || count(8) > 1 || count(10) > 2 {
29        header_error(wire, response, Rcode::FORMERR, false);
30        return Admission::Respond;
31    }
32    let Ok(query) = Message::parse(wire) else {
33        header_error(wire, response, Rcode::FORMERR, false);
34        return Admission::Respond;
35    };
36    let Some(question) = query.questions().next() else {
37        header_error(wire, response, Rcode::SERVFAIL, false);
38        return Admission::Respond;
39    };
40    if !allow_non_in && question.qclass != Class::IN {
41        write_error(query, response, Rcode::REFUSED);
42        return Admission::Respond;
43    }
44    // RFC 6891 §6.1.3: an unsupported EDNS version requires BADVERS.
45    if query
46        .badvers_response(response)
47        .expect("valid BADVERS Response")
48        .is_some()
49    {
50        return Admission::Respond;
51    }
52    Admission::Query(query)
53}
54
55fn header_error(wire: &[u8], response: &mut Vec<u8>, rcode: Rcode, keep_opcode: bool) {
56    let flags = u16::from_be_bytes([wire[2], wire[3]]);
57    let flags = 0x8000 | (flags & 0x03b0) | if keep_opcode { flags & 0x7800 } else { 0 } | rcode.0;
58    response.clear();
59    response.extend_from_slice(&wire[..2]);
60    response.extend_from_slice(&flags.to_be_bytes());
61    response.extend_from_slice(&[0; 8]);
62}
63
64pub(crate) fn write_error(query: Message<'_>, response: &mut Vec<u8>, rcode: Rcode) {
65    // RFC 1035 §4.1.1: QR marks a response; the opcode and RD are copied from the query.
66    let flags = 0x8000
67        | (query.flags() & 0x7800)
68        | if query.opcode() == Opcode::QUERY {
69            query.flags() & 0x0110
70        } else {
71            0
72        }
73        | (rcode.0 & 15);
74    let mut builder = MessageBuilder::new(response, usize::from(u16::MAX), query.id(), flags)
75        .expect("valid maximum");
76    if let Some(question) = query.questions().next() {
77        let _ = builder.question(question.name, question.qtype, question.qclass);
78    }
79    let _ = builder.finish();
80}