cdns_rs/
cfg_host_parser.rs1use std::borrow::Borrow;
19use std::collections::HashSet;
20use std::hash::{Hash, Hasher};
21use std::net::IpAddr;
22
23use crate::common::HOST_CFG_PATH;
24use crate::{error::*, internal_error_map, write_error, QType};
25use crate::tokenizer::*;
26
27
28#[derive(Clone, Debug)]
32pub struct HostnameEntry
33{
34 ip: IpAddr,
35 hostnames: Vec<String>,
36}
37
38impl Eq for HostnameEntry {}
39
40impl PartialEq for HostnameEntry
41{
42 fn eq(&self, other: &HostnameEntry) -> bool
43 {
44 return self.ip == other.ip;
45 }
46}
47
48impl Borrow<IpAddr> for HostnameEntry
49{
50 fn borrow(&self) -> &IpAddr
51 {
52 return &self.ip;
53 }
54}
55
56impl Hash for HostnameEntry
57{
58 fn hash<H: Hasher>(&self, state: &mut H)
59 {
60 self.ip.hash(state);
61 }
62}
63
64
65impl HostnameEntry
66{
67 pub
69 fn get_ip(&self) -> &IpAddr
70 {
71 return &self.ip;
72 }
73
74 pub
76 fn get_hostnames(&self) -> &[String]
77 {
78 return self.hostnames.as_slice();
79 }
80
81 pub
83 fn get_hostnames_iter(&self) -> std::slice::Iter<'_, String>
84 {
85 return self.hostnames.iter();
86 }
87}
88
89#[derive(Clone, Debug)]
90pub struct HostConfig
91{
92 hostnames: HashSet<HostnameEntry>
93}
94
95impl Default for HostConfig
96{
97 fn default() -> Self
98 {
99 return
100 Self
101 {
102 hostnames: Default::default(),
103 };
104 }
105}
106
107impl HostConfig
108{
109 pub
110 fn is_empty(&self) -> bool
111 {
112 return self.hostnames.is_empty();
113 }
114
115 pub
116 fn search_by_ip(&self, ip: &IpAddr) -> Option<&HostnameEntry>
117 {
118 return self.hostnames.get(ip);
119 }
120
121 pub
123 fn search_by_fqdn(&self, qtype: &QType, name: &str) -> Option<&HostnameEntry>
124 {
125 for host in self.hostnames.iter()
126 {
127 for fqdn in host.hostnames.iter()
128 {
129 if name == fqdn.as_str() && qtype.ipaddr_match(&host.ip)
131 {
132 return Some(host);
133 }
134 }
135 }
136
137 return None;
138 }
139
140 pub
141 fn parse_host_file_internal(file_content: String) -> CDnsResult<Self>
142 {
143 let mut tk = ConfTokenizer::from_str(&file_content)?;
144 let mut he_list: HashSet<HostnameEntry> = HashSet::new();
145
146 loop
147 {
148 let field_ip = tk.read_next()?;
149
150 if field_ip.is_none() == true
151 {
152 break;
154 }
155
156 let ip: IpAddr =
159 match field_ip.unwrap().parse()
160 {
161 Ok(r) => r,
162 Err(_e) =>
163 {
164 tk.skip_upto_eol();
166 continue;
167 }
168 };
169
170 let hostnames = tk.read_upto_eol()?;
171
172 if hostnames.len() > 0
173 {
174 let he = HostnameEntry{ ip: ip, hostnames: hostnames };
175 he_list.insert(he);
176 }
177 else
178 {
179 write_error!(
180 internal_error_map!(CDnsErrorType::ConfigError,
181 "in file: '{}' IP is not defined with domain name: '{}'\n", HOST_CFG_PATH, ip)
182 );
183 }
184 }
185
186 if he_list.len() == 0
187 {
188 write_error!(
189 internal_error_map!(CDnsErrorType::ConfigError, "file: '{}' file is empty or damaged!\n",
190 HOST_CFG_PATH)
191 );
192 }
193
194 return Ok(
195 Self
196 {
197 hostnames: he_list
198 }
199 );
200 }
201}
202
203#[cfg(test)]
204mod tests
205{
206 use std::net::IpAddr;
207
208 use crate::cfg_host_parser::HostConfig;
209
210 #[test]
211 fn test_parse_host_file_0()
212 {
213 let hosts1: Vec<&'static str> = vec!["debian-laptop"];
214 let hosts2: Vec<&'static str> = vec!["localhost", "ip6-localhost", "ip6-loopback"];
215 let hosts3: Vec<&'static str> = vec!["ip6-allnodes"];
216 let hosts4: Vec<&'static str> = vec!["ip6-allrouters"];
217
218 let ip1: IpAddr = "127.0.1.1".parse().unwrap();
219 let ip2: IpAddr = "::1".parse().unwrap();
220 let ip3: IpAddr = "ff02::1".parse().unwrap();
221 let ip4: IpAddr = "ff02::2".parse().unwrap();
222
223 let ip_list =
224 vec![
225 (ip1, hosts1),
226 (ip2, hosts2),
227 (ip3, hosts3),
228 (ip4, hosts4)
229 ];
230
231 let test =
232 "127.0. 0.1 localhost
233 127.0.1.1 debian-laptop
234
235 # The following lines are desirable for IPv6 capable hosts
236 ::1 localhost ip6-localhost ip6-loopback
237 ff02::1 ip6-allnodes
238 ff02::2 ip6-allrouters".to_string();
239
240 let p = HostConfig::parse_host_file_internal(test);
241 assert_eq!(p.is_ok(), true, "{}", p.err().unwrap());
242
243 let p = p.unwrap();
244
245 for (ip, host) in ip_list
246 {
247 let res = p.hostnames.get(&ip);
248 assert_eq!(res.is_some(), true);
249
250 let res = res.unwrap();
251
252 assert_eq!(res.hostnames, host);
253 }
254
255 return;
256 }
257
258 #[test]
259 fn test_parse_host_file()
260 {
261
262 let ip0:IpAddr = "127.0.0.1".parse().unwrap();
263 let ip1:IpAddr = "127.0.1.1".parse().unwrap();
264 let ip2:IpAddr = "::1".parse().unwrap();
265 let ip3:IpAddr = "ff02::1".parse().unwrap();
266 let ip4:IpAddr = "ff02::2".parse().unwrap();
267
268 let hosts0: Vec<&'static str> = vec!["localhost"];
269 let hosts1: Vec<&'static str> = vec!["debian-laptop"];
270 let hosts2: Vec<&'static str> = vec!["localhost", "ip6-localhost", "ip6-loopback"];
271 let hosts3: Vec<&'static str> = vec!["ip6-allnodes"];
272 let hosts4: Vec<&'static str> = vec!["ip6-allrouters"];
273
274 let ip_list =
275 vec![
276 (ip0, hosts0),
277 (ip1, hosts1),
278 (ip2, hosts2),
279 (ip3, hosts3),
280 (ip4, hosts4)
281 ];
282
283 let test =
284 "127.0.0.1 localhost
285 127.0.1.1 debian-laptop
286
287 # The following lines are desirable for IPv6 capable hosts
288 ::1 localhost ip6-localhost ip6-loopback
289 ff02::1 ip6-allnodes
290 ff02::2 ip6-allrouters".to_string();
291
292
293 let p = HostConfig::parse_host_file_internal(test);
294 assert_eq!(p.is_ok(), true, "{}", p.err().unwrap());
295
296 let p = p.unwrap();
297
298 for (ip, host) in ip_list
299 {
300 let res = p.hostnames.get(&ip);
301 assert_eq!(res.is_some(), true);
302
303 let res = res.unwrap();
304
305 assert_eq!(res.hostnames, host);
306 }
307 }
308
309 #[test]
310 fn test_parse_host_file_2()
311 {
312
313 let ip0:IpAddr = "127.0.0.1".parse().unwrap();
314 let ip1:IpAddr = "127.0.1.1".parse().unwrap();
315 let ip2:IpAddr = "::1".parse().unwrap();
316 let ip3:IpAddr = "ff02::1".parse().unwrap();
317 let ip4:IpAddr = "ff02::2".parse().unwrap();
318
319 let hosts0: Vec<&'static str> = vec!["localhost", "localdomain", "domain.local"];
320 let hosts1: Vec<&'static str> = vec!["debian-laptop", "test123.domain.tld"];
321 let hosts2: Vec<&'static str> = vec!["localhost", "ip6-localhost", "ip6-loopback"];
322 let hosts3: Vec<&'static str> = vec!["ip6-allnodes"];
323 let hosts4: Vec<&'static str> = vec!["ip6-allrouters"];
324
325
326 let ip_list =
327 vec![
328 (ip0, hosts0),
329 (ip1, hosts1),
330 (ip2, hosts2),
331 (ip3, hosts3),
332 (ip4, hosts4)
333 ];
334
335 let test =
336 "127.0.0.1 localhost localdomain domain.local
337 127.0.1.1 debian-laptop test123.domain.tld
338
339 # The following lines are desirable for IPv6 capable hosts
340 #
341 #
342 ::1 localhost ip6-localhost ip6-loopback
343 ff02::1 ip6-allnodes
344 ff02::2 ip6-allrouters
345 ".to_string();
346
347 let p = HostConfig::parse_host_file_internal(test);
348 assert_eq!(p.is_ok(), true, "{}", p.err().unwrap());
349
350 let p = p.unwrap();
351
352 for (ip, host) in ip_list
353 {
354 let res = p.hostnames.get(&ip);
355 assert_eq!(res.is_some(), true);
356
357 let res = res.unwrap();
358
359 assert_eq!(res.hostnames, host);
360 }
361 }
362
363}