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