Skip to main content

dnsbox/rdata/
wks.rs

1//! WKS record data (RFC 1035 §3.4.2).
2
3use core::fmt;
4use core::net::Ipv4Addr;
5
6use super::{ComposeRdata, ParseRdata, ParseRdataText};
7use crate::wire::{Composer, OutBuf, WireReader};
8use crate::zone::{Scanner, Token};
9use crate::{Class, Error, Result, Rtype};
10
11/// `WKS` record data: well-known services offered by a host
12/// (RFC 1035 §3.4.2). Class IN only. Deprecated in practice (RFC 1123
13/// §2.2).
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct Wks<'a> {
16    /// The host address.
17    pub address: Ipv4Addr,
18    /// IP protocol number (6 = TCP, 17 = UDP).
19    pub protocol: u8,
20    /// Port bitmap: bit *n* (MSB-first) set means port *n* is offered.
21    pub bitmap: &'a [u8],
22}
23
24impl<'a> Wks<'a> {
25    /// Iterates over the ports set in the bitmap, ascending.
26    pub fn ports(&self) -> impl Iterator<Item = u16> + 'a {
27        let bitmap = self.bitmap;
28        bitmap.iter().enumerate().flat_map(|(i, &byte)| {
29            (0..8u16).filter_map(move |bit| {
30                let port = u16::try_from(i * 8).ok()?.checked_add(bit)?;
31                (byte & (0x80 >> bit) != 0).then_some(port)
32            })
33        })
34    }
35}
36
37impl<'a> ParseRdata<'a> for Wks<'a> {
38    const RTYPE: Rtype = Rtype::WKS;
39    const CLASS: Option<Class> = Some(Class::IN);
40
41    fn parse_rdata(rdata: &mut WireReader<'a>) -> Result<Self> {
42        Ok(Wks {
43            address: Ipv4Addr::from(rdata.read_array::<4>()?),
44            protocol: rdata.read_u8()?,
45            bitmap: rdata.read_rest(),
46        })
47    }
48}
49
50impl ComposeRdata for Wks<'_> {
51    fn rtype(&self) -> Rtype {
52        Rtype::WKS
53    }
54
55    fn compose_rdata<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
56        c.put_bytes(&self.address.octets())?;
57        c.put_u8(self.protocol)?;
58        c.put_bytes(self.bitmap)
59    }
60}
61
62impl fmt::Display for Wks<'_> {
63    /// `address protocol port...`, with numeric protocol and ports.
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "{} {}", self.address, self.protocol)?;
66        for port in self.ports() {
67            write!(f, " {port}")?;
68        }
69        Ok(())
70    }
71}
72
73/// Protocol mnemonics accepted in WKS text (IANA "Assigned Internet
74/// Protocol Numbers"; BIND asks the system's protocol database).
75const PROTOCOLS: &[(&str, u8)] = &[("ICMP", 1), ("IGMP", 2), ("TCP", 6), ("UDP", 17)];
76
77/// Service names accepted in WKS text (IANA "Service Name and Transport
78/// Protocol Port Number Registry"; BIND asks the system's services
79/// database).
80const SERVICES: &[(&str, u16)] = &[
81    ("echo", 7),
82    ("discard", 9),
83    ("daytime", 13),
84    ("ftp-data", 20),
85    ("ftp", 21),
86    ("ssh", 22),
87    ("telnet", 23),
88    ("smtp", 25),
89    ("time", 37),
90    ("whois", 43),
91    ("nicname", 43),
92    ("domain", 53),
93    ("tftp", 69),
94    ("gopher", 70),
95    ("finger", 79),
96    ("http", 80),
97    ("www", 80),
98    ("kerberos", 88),
99    ("pop3", 110),
100    ("sunrpc", 111),
101    ("auth", 113),
102    ("nntp", 119),
103    ("ntp", 123),
104    ("imap", 143),
105    ("snmp", 161),
106    ("ldap", 389),
107    ("https", 443),
108    ("submission", 587),
109];
110
111/// A number, or a mnemonic from `table` (case-insensitively).
112fn lookup<'t, T: Copy>(
113    t: Token<'t>,
114    table: &[(&str, T)],
115    number: fn(&Token<'t>) -> Result<T>,
116) -> Result<T> {
117    if t.as_bytes().first().is_some_and(u8::is_ascii_digit) {
118        return number(&t);
119    }
120    table
121        .iter()
122        .find(|(name, _)| t.is(name))
123        .map(|&(_, v)| v)
124        .ok_or(Error::UnknownMnemonic)
125}
126
127impl ParseRdataText for Wks<'_> {
128    /// `<address> <protocol> <service>...` (RFC 1035 §3.4.2; RFC 1010
129    /// mnemonics): the protocol and the services are numbers or common
130    /// mnemonics (`TCP`, `UDP`; `smtp`, `domain`, ...), the services in
131    /// any order, possibly none.
132    fn parse_text<B: OutBuf + ?Sized>(s: &mut Scanner<'_>, out: &mut B) -> Result<()> {
133        out.put_bytes(&s.ipv4()?.octets())?;
134        out.put_u8(lookup(s.word()?, PROTOCOLS, Token::u8)?)?;
135        // One bit per port: the work is linear in the number of tokens.
136        let mut bitmap = [0u8; 8192];
137        let mut len = 0;
138        while let Some(t) = s.next_token()? {
139            if t.is_quoted() {
140                return Err(Error::InvalidText);
141            }
142            let port = usize::from(lookup(t, SERVICES, Token::u16)?);
143            if let Some(byte) = bitmap.get_mut(port / 8) {
144                *byte |= 0x80 >> (port % 8);
145            }
146            len = len.max(port / 8 + 1);
147        }
148        out.put_bytes(bitmap.get(..len).unwrap_or(&[]))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use crate::rdata::tests::{text_error, text_parse, text_round_trip};
155    use crate::{Error, Rtype};
156
157    #[test]
158    fn text() {
159        // RFC 1035 §3.4.2 / BIND style.
160        text_round_trip(
161            Rtype::WKS,
162            "10.0.0.1 tcp ( smtp 37 )",
163            &[10, 0, 0, 1, 6, 0x00, 0x00, 0x00, 0x40, 0x04],
164            "10.0.0.1 6 25 37",
165        );
166        text_round_trip(Rtype::WKS, "192.0.2.1 17", &[192, 0, 2, 1, 17], "192.0.2.1 17");
167        text_round_trip(
168            Rtype::WKS,
169            "192.0.2.1 UDP 1023 domain 0 DOMAIN",
170            &{
171                let mut w = std::vec![192, 0, 2, 1, 17];
172                w.resize(5 + 128, 0);
173                w[5] = 0x80;
174                w[5 + 6] = 0x04;
175                w[5 + 127] = 0x01;
176                w
177            },
178            "192.0.2.1 17 0 53 1023",
179        );
180        let top = text_parse(Rtype::WKS, "192.0.2.1 6 65535").unwrap();
181        assert_eq!((top.len(), top.last()), (5 + 8192, Some(&0x01)));
182        assert_eq!(text_error(Rtype::WKS, "10.0.0.1 xtp 25"), Error::UnknownMnemonic);
183        assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 bogus"), Error::UnknownMnemonic);
184        assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 65536"), Error::InvalidText);
185        assert_eq!(text_error(Rtype::WKS, "10.0.0.1 256 25"), Error::InvalidText);
186        assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 \"25\""), Error::InvalidText);
187        assert_eq!(text_error(Rtype::WKS, "10.0.0.1"), Error::UnexpectedEof);
188    }
189}