1use std::collections::{BTreeSet, HashMap};
17use std::convert::TryFrom;
18use std::io::ErrorKind;
19use std::net::{IpAddr, SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};
20use std::vec;
21use std::sync::Arc;
22use std::time::Duration;
23use std::time::Instant;
24
25use crate::cfg_resolv_parser::{ConfigEntryTls, ResolveConfEntry, ResolveConfig, ResolveConfigFamily};
26use crate::{error::*, write_error};
27use crate::query::{QDnsQuery, QDnsQueryResult, QuerySetup};
28use crate::sync::network::NetworkTap;
29use crate::internal_error;
30use crate::query_private::QDnsReq;
31use crate::common::*;
32
33use super::caches::CACHE;
34use super::network::SocketTap;
35
36#[cfg(feature = "use_sync_tls")]
37use super::network::with_tls::{TcpHttpsConnection, TcpTlsConnection};
38
39#[derive(Clone, Debug)]
43pub enum QDnsSockerAddr
44{
45 Ip(SocketAddr),
46 Host(String, u16)
47}
48
49impl ToSocketAddrs for QDnsSockerAddr
50{
51 type Iter = vec::IntoIter<SocketAddr>;
52
53 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter>
54 {
55 match self
56 {
57 QDnsSockerAddr::Ip(socket_addr) =>
58 return Ok(vec![socket_addr.clone()].into_iter()),
59 QDnsSockerAddr::Host(host, port) =>
60 {
61 let qdns =
62 QDns
63 ::make_a_aaaa_request(None, host, QuerySetup::default())
64 .map_err(|e|
65 std::io::Error::new(ErrorKind::NotFound,
66 CDnsSuperError::from(e))
67 )?;
68
69 let res =
70 qdns
71 .query()
72 .collect_ok()
73 .into_iter()
74 .map(|resp|
75 resp
76 .get_responses()
77 .iter()
78 .map(|r|
79 r.get_rdata().get_ip().map(|f| SocketAddr::new(f, *port))
80 )
81 .collect::<Vec<Option<SocketAddr>>>()
82 )
83 .flatten()
84 .filter(|p| p.is_some())
85 .map(|v| v.unwrap())
86 .collect::<Vec<SocketAddr>>();
87
88 return Ok( res.into_iter() );
89 }
90 }
91 }
92}
93
94
95impl QDnsSockerAddr
96{
97 pub
106 fn resolve<D>(host: D) -> std::io::Result<Self>
107 where D: AsRef<str>
108 {
109 if let Ok(addr) = host.as_ref().parse::<SocketAddr>()
110 {
111 return Ok(Self::Ip(addr));
112 }
113 else
114 {
115 let ref_host = host.as_ref();
116
117 let (domain, port) =
118 match ref_host.split_once(":")
119 {
120 Some((h, portno)) =>
121 {
122 let port: u16 =
123 portno
124 .parse()
125 .map_err(|e|
126 std::io::Error::new(ErrorKind::InvalidData, format!("{}", e))
127 )?;
128
129 (h, port)
130 },
131 None =>
132 return Err(std::io::Error::new(ErrorKind::InvalidData, "missing prot number"))
133 };
134
135 return Ok(Self::Host(domain.to_string(), port));
136 }
137 }
138
139 pub
148 fn resolve_port<D>(host: D, port: u16) -> std::io::Result<Self>
149 where D: AsRef<str>
150 {
151 if let Ok(addr) = host.as_ref().parse::<IpAddr>()
152 {
153 return Ok(Self::Ip(SocketAddr::new(addr, port)));
154 }
155 else
156 {
157 return Ok(Self::Host(host.as_ref().to_string(), port));
158 }
159 }
160}
161
162#[derive(Clone, Debug)]
173pub struct QDns
174{
175 resolvers: Arc<ResolveConfig>,
177
178 ordered_req_list: Vec<QDnsReq>,
180
181 opts: QuerySetup,
183}
184
185impl QDns
186{
187 pub
207 fn make_empty(resolvers: Option<Arc<ResolveConfig>>, opts: QuerySetup) -> CDnsResult<Self>
208 {
209 return Ok(
210 Self
211 {
212 resolvers: resolvers.unwrap_or(CACHE.clone_resolve_list()?),
213 ordered_req_list: Vec::new(),
214 opts: opts,
215 }
216 );
217 }
218
219 pub
227 fn add_request<R>(&mut self, qtype: QType, req_name: R) -> CDnsResult<()>
228 where R: TryInto<QDnsName, Error = CDnsError>
229 {
230 let qr = QDnsReq::new_into(req_name, qtype)?;
231
232 self.ordered_req_list.push(qr);
233
234 return Ok(());
235 }
236
237 pub
261 fn make_a_aaaa_request<R: AsRef<str>>(resolvers_opt: Option<Arc<ResolveConfig>>, req_name_ref: R,
262 opts: QuerySetup) -> CDnsResult<Self>
263 {
264 let req_n = QDnsName::try_from(req_name_ref.as_ref())?;
265
266 let resolvers = resolvers_opt.unwrap_or(CACHE.clone_resolve_list()?);
267
268 let reqs: Vec<QDnsReq> =
270 match resolvers.family
271 {
272 ResolveConfigFamily::INET4_INET6 =>
273 {
274 vec![
275 QDnsReq::new(req_n.clone(), QType::A),
276 QDnsReq::new(req_n, QType::AAAA),
277 ]
278 },
279 ResolveConfigFamily::INET6_INET4 =>
280 {
281 vec![
282 QDnsReq::new(req_n.clone(), QType::AAAA),
283 QDnsReq::new(req_n, QType::A),
284 ]
285 },
286 ResolveConfigFamily::INET6 =>
287 {
288 vec![
289 QDnsReq::new(req_n, QType::AAAA),
290 ]
291 },
292 ResolveConfigFamily::INET4 =>
293 {
294 vec![
295 QDnsReq::new(req_n, QType::A),
296 ]
297 }
298 _ =>
299 {
300 vec![
303 QDnsReq::new(req_n.clone(), QType::A),
304 QDnsReq::new(req_n, QType::AAAA),
305 ]
306 }
307 };
308
309
310
311 return Ok(
312 Self
313 {
314 resolvers: resolvers,
315 ordered_req_list: reqs,
316 opts: opts,
317 }
318 );
319 }
320
321 pub
328 fn query(mut self) -> QDnsQueryResult
329 {
330 let now =
332 if self.opts.measure_time == true
333 {
334 Some(Instant::now())
335 }
336 else
337 {
338 None
339 };
340
341 if self.resolvers.lookup.is_file_first()
344 {
345 let mut query_res =
346 match self.lookup_file(now.as_ref())
347 {
348 Ok(file) =>
349 {
350 if file.is_empty() == false
351 {
352 self.ordered_req_list.retain(|req|
354 {
355 return !file.contains_dnsreq(req);
356 }
357 );
358 }
359
360 file
361 },
362 Err(e) =>
363 {
364 write_error!(e);
365
366 QDnsQueryResult::default()
367 }
368 };
369
370
371 if self.ordered_req_list.is_empty() == false && self.resolvers.lookup.is_bind() == true
373 {
374 let res = self.process_request(now.as_ref());
375
376 query_res.extend(res);
377 }
378
379 return query_res;
380 }
381 else
382 {
383 let mut dns_res = self.process_request(now.as_ref());
384 if dns_res.is_empty() == false
385 {
386 self.ordered_req_list.retain(|req|
388 {
389 return !dns_res.contains_dnsreq(req);
390 }
391 );
392 }
393
394
395
396 if self.ordered_req_list.is_empty() == false && self.resolvers.lookup.is_file() == true
397 {
398 match self.lookup_file(now.as_ref())
399 {
400 Ok(res) =>
401 {
402 dns_res.extend(res);
403 },
404 Err(e) =>
405 {
406 write_error!(e);
407 }
408 }
409 }
410
411 return dns_res;
412 }
413 }
414
415 fn get_timeout(&self) -> Duration
417 {
418 if let Some(timeout) = self.opts.timeout
419 {
420 return Duration::from_secs(timeout as u64);
421 }
422 else
423 {
424 return Duration::from_secs(self.resolvers.timeout as u64);
425 }
426 }
427
428 fn lookup_file(&mut self, now: Option<&Instant>) -> CDnsResult<QDnsQueryResult>
430 {
431 let mut dnsquries: QDnsQueryResult = QDnsQueryResult::default();
432
433 if self.opts.ign_hosts == false
435 {
436 let hlist = CACHE.clone_host_list()?;
437
438 for req in self.ordered_req_list.iter()
439 {
440 match *req.get_type()
441 {
442 QType::A | QType::AAAA =>
443 {
444 let req_name = String::from(req.get_req_name());
445
446 let Some(host_name_ent) = hlist.search_by_fqdn(req.get_type(), req_name.as_str())
447 else { continue };
448
449 let Some(drp) = DnsResponsePayload::new_local(*req.get_type(), host_name_ent)
450 else { continue };
451
452 dnsquries.push(req.clone(), Ok(QDnsQuery::from_local(drp, now)));
454 },
455 QType::PTR =>
456 {
457 let Ok(ip) = IpAddr::try_from(req.get_req_name())
458 else { continue };
459
460 let Some(host_name_ent) = hlist.search_by_ip(&ip)
461 else { continue };
462
463 let Some(drp) = DnsResponsePayload::new_local(*req.get_type(), host_name_ent)
464 else { continue };
465
466 dnsquries.push(req.clone(), Ok(QDnsQuery::from_local(drp, now)));
467 },
468 _ =>
469 continue,
470 }
471 }
472
473
474 }
475
476 return Ok(dnsquries);
477 }
478
479
480 #[inline]
483 fn create_socket(&self, force_tcp: bool, nonblk_flag: bool, resolver: Arc<ResolveConfEntry>) -> CDnsResult<Box<dyn SocketTap>>
484 {
485 let is_tls = resolver.get_tls_type();
486
487 if is_tls == ConfigEntryTls::Tls
488 {
489 #[cfg(feature = "use_sync_tls")]
490 return
491 NetworkTap
492 ::<TcpTlsConnection>
493 ::new_tls(resolver, self.get_timeout(), nonblk_flag, CDdnsGlobals::get_tcp_conn_timeout());
494 #[cfg(not(feature = "use_sync_tls"))]
495 internal_error!(CDnsErrorType::SocketNotSupported,
496 "socket not supported: '{}'", resolver.get_tls_type());
497 }
498 else if is_tls == ConfigEntryTls::Https
499 {
500 #[cfg(feature = "use_sync_tls")]
501 return
502 NetworkTap
503 ::<TcpHttpsConnection>
504 ::new_https(resolver, self.get_timeout(), nonblk_flag, CDdnsGlobals::get_tcp_conn_timeout());
505 #[cfg(not(feature = "use_sync_tls"))]
506 internal_error!(CDnsErrorType::SocketNotSupported,
507 "socket not supported: '{}'", resolver.get_tls_type());
508 }
509 else if self.resolvers.option_flags.is_force_tcp() == true || force_tcp == true
510 {
511 return
512 NetworkTap
513 ::<TcpStream>
514 ::new_tcp(resolver, nonblk_flag, self.get_timeout(), CDdnsGlobals::get_tcp_conn_timeout());
515 }
516 else
517 {
518 return
519 NetworkTap
520 ::<UdpSocket>
521 ::new_udp(resolver, nonblk_flag, self.get_timeout());
522 }
523 }
524
525
526 fn process_request(&mut self, now: Option<&Instant>) -> QDnsQueryResult
528 {
529 let mut responses: QDnsQueryResult = QDnsQueryResult::with_capacity(self.ordered_req_list.len());
530
531 if self.resolvers.option_flags.is_no_parallel() == true
532 {
533 for req in self.ordered_req_list.iter()
534 {
535 let mut last_resp: Option<CDnsResult<QDnsQuery>> = None;
536
537 for resolver in self.resolvers.get_resolvers_iter()
538 {
539 match self.query_exec_seq(now, resolver.clone(), req, None)
540 {
541 Ok(resp) =>
542 {
543 if resp.should_check_next_ns() == true
544 {
545 last_resp = Some(Ok(resp));
546
547 continue;
548 }
549 else
550 {
551 responses.push(req.clone(), Ok(resp));
552
553 let _ = last_resp.take();
554
555 break;
556 }
557 },
558 Err(e) =>
559 {
560 if last_resp.is_none() == true
561 {
562 last_resp = Some(Err(e));
563 }
564
565 continue;
566 }
567 }
568 } responses.push(req.clone(), last_resp.take().unwrap());
571 }}
573 else
574 {
575 for resolver in self.resolvers.get_resolvers_iter()
578 {
579 if self.ordered_req_list.is_empty() == true
580 {
581 break;
582 }
583
584 match self.query_exec_pipelined(now, resolver.clone(), None)
585 {
586 Ok(resp) =>
587 {
588 for (qdns_res, qdns_que) in resp
589 {
590 if let Ok(ref resp) = qdns_que
591 {
592 if resp.should_check_next_ns() == false
593 {
594 self
595 .ordered_req_list
596 .retain(
597 |req_item|
598 req_item != &qdns_res
599 );
600 }
601 }
602
603 responses.push(qdns_res, qdns_que);
604 }
605 },
606 Err(e) =>
607 {
608 write_error!(e);
609
610 continue;
611 }
612 }
613 }
614 }
615
616 return responses;
617 }
618
619 fn query_exec_pipelined(
622 &self,
623 now: Option<&Instant>,
624 resolver: Arc<ResolveConfEntry>,
625 requery: Option<HashMap<DnsRequestHeader, QDnsReq>>,
626 ) -> CDnsResult<QDnsQueryResult>
627 {
628 let force_tcp = self.resolvers.option_flags.is_force_tcp() || requery.is_some();
629
630 let mut query_headers: HashMap<DnsRequestHeader, QDnsReq> =
631 if let Some(requer) = requery
632 {
633 let pkts_ids = requer.iter().map(|q| q.0.get_id()).collect::<BTreeSet<u16>>();
634
635 requer
637 .into_iter()
638 .map(
639 |(mut qrr, qdr)|
640 {
641 loop
642 {
643 qrr.regenerate_id();
644
645 if pkts_ids.contains(&qrr.get_id()) == false
646 {
647 break;
648 }
649 }
650 (qrr, qdr)
651 })
652 .collect::<HashMap<DnsRequestHeader, QDnsReq>>()
653 }
654 else
655 {
656 let mut pkts_ids: BTreeSet<u16> = BTreeSet::new();
657
658 self
660 .ordered_req_list
661 .iter()
662 .map(
663 |query|
664 {
665
666 let mut drh_res = DnsRequestHeader::try_from(query);
667
668 loop
669 {
670 if let Ok(ref mut drh) = drh_res
671 {
672 if pkts_ids.contains(&drh.get_id()) == true
673 {
674 drh.regenerate_id();
675
676 continue;
677 }
678 else
679 {
680 pkts_ids.insert(drh.get_id());
681 break;
682 }
683 }
684 else
685 {
686 break;
687 }
688 }
689
690 drh_res.map(|dh| (dh, query.clone()))
691 }
692 )
693 .collect::<CDnsResult<HashMap<DnsRequestHeader, QDnsReq>>>()?
694 };
695
696 let tap =
698 self.create_socket(force_tcp, false, resolver.clone())?;
699
700 for qh in query_headers.iter()
702 {
703 let pkt = qh.0.to_bytes(tap.should_append_len())?;
704
705 tap.send(pkt.as_slice())?;
706 }
707
708 let mut resp = QDnsQueryResult::with_capacity(self.ordered_req_list.len());
709 let mut requery: HashMap<DnsRequestHeader, QDnsReq> = HashMap::new();
710 loop
713 {
714 if query_headers.len() == 0
715 {
716 break;
717 }
718
719 if tap.poll_read()? == false
720 {
721 break;
723 }
724
725 let ans = tap.recv()?;let Some((query_header, qdnsreq)) =
728 query_headers.remove_entry(&ans.req_header)
729 else
730 {
731 internal_error!(CDnsErrorType::IoError,
732 "can not find response with request: {}", ans.req_header);
733 };
734
735 ans.verify(&query_header)?;
736
737 let qdns_resp =
739 QDnsQuery::from_response(tap.get_remote_addr(), ans, now);
740
741 if let Ok(ref qdns) = qdns_resp
742 {
743 if qdns.get_status().should_try_tcp() == true && force_tcp == false
744 {
745 requery.insert(query_header, qdnsreq);
746 }
747 else
748 {
749 resp.push(qdnsreq, qdns_resp);
750 }
751 }
752 else
753 {
754 resp.push(qdnsreq, qdns_resp);
755 }
756 }
757
758 if requery.is_empty() == false
759 {
760 let res = self.query_exec_pipelined(now, resolver, Some(requery))?;
761
762 resp.extend(res);
763 }
764
765 return Ok(resp);
766 }
767
768 fn query_exec_seq(
770 &self,
771 now: Option<&Instant>,
772 resolver: Arc<ResolveConfEntry>,
773 query: &QDnsReq,
774 requery: Option<DnsRequestHeader>,
775 ) -> CDnsResult<QDnsQuery>
776 {
777 let force_tcp = self.resolvers.option_flags.is_force_tcp() || requery.is_some();
778
779
780
781 let query_header =
782 if let Some(mut requery) = requery
784 {
785 requery.regenerate_id();
786
787 requery
788 }
789 else
790 {
791 let drh_req = DnsRequestHeader::try_from(query)?;
792
793 drh_req
794 };
795
796 let res =
797 {
798 let tap =
800 self
801 .create_socket(force_tcp, false, resolver.clone())?;
802
803 let pkt = query_header.to_bytes(tap.should_append_len())?;
805
806 tap.send(pkt.as_slice())?;
808
809 let ans = tap.recv()?; ans.verify(&query_header)?;
812
813 let resp = QDnsQuery::from_response(tap.get_remote_addr(), ans, now)?;
815
816 Ok(resp)
817 };
818
819 if (res.is_ok() == true && res.as_ref().unwrap().status.should_try_tcp() == false) ||
820 (res.is_err() == true && force_tcp == true)
821 {
822 return res;
823 }
824
825
826 return
827 self.query_exec_seq(now, resolver.clone(), query, Some(query_header));
828 }
829
830 }
889
890#[cfg(test)]
891
892mod tests
893{
894 use std::net::IpAddr;
895
896 use crate::{common::{byte2hexchar, ip2pkt, RecordPTR, RecordReader}, sync::{query::QDns}, QDnsQueryRec, QType, QuerySetup};
897
898 #[test]
899 fn test_ip2pkt()
900 {
901 use std::time::Instant;
902 use std::net::{IpAddr, Ipv4Addr};
903
904 let test = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
905
906 let now = Instant::now();
907
908 let res = ip2pkt(&test);
909
910 let elapsed = now.elapsed();
911 println!("Elapsed: {:.2?}", elapsed);
912
913 let ctrl = b"\x01\x38\x01\x38\x01\x38\x01\x38\x07\x69\x6e\x2d\x61\x64\x64\x72\x04\x61\x72\x70\x61\x00";
914
915 assert_eq!(res.as_slice(), ctrl);
916 }
917
918
919 #[test]
920 fn test_byte2hexchar()
921 {
922 assert_eq!(byte2hexchar(1), 0x31);
923 assert_eq!(byte2hexchar(9), 0x39);
924 assert_eq!(byte2hexchar(10), 'a' as u8);
925 assert_eq!(byte2hexchar(15), 'f' as u8);
926 }
927
928 #[test]
929 fn reverse_lookup_test()
930 {
931 use std::time::Instant;
932
933 let ipp: IpAddr = "8.8.8.8".parse().unwrap();
934 let mut query_setup = QuerySetup::default();
937 query_setup.set_measure_time(true);
938
939 let now = Instant::now();
940
941 let mut dns_req =
942 QDns::make_empty(None, query_setup).unwrap();
943
944 dns_req.add_request(QType::PTR, ipp).unwrap();
945 let res = dns_req.query();
946
947 let elapsed = now.elapsed();
948 println!("Elapsed: {:.2?}", elapsed);
949
950 println!("{}", res);
951
952 assert_eq!(res.is_empty(), false);
953
954 let recs = res.collect_ok();
955 let rec = &recs[0];
956 assert_eq!(rec.status, QDnsQueryRec::Ok);
958
959 assert_eq!(rec.resp.len(), 1);
960 assert_eq!(rec.resp[0].rdata, RecordPTR::wrap(RecordPTR{ fqdn: "dns.google".to_string() }));
961 }
962
963 #[test]
964 fn reverse_lookup_hosts_test()
965 {
966 use std::time::Instant;
967
968 let ipp: IpAddr = "127.0.0.1".parse().unwrap();
969 let now = Instant::now();
972
973 let mut query_setup = QuerySetup::default();
974 query_setup.set_measure_time(true);
975
976 let mut dns_req =
977 QDns::make_empty(None, query_setup).unwrap();
978
979 dns_req.add_request(QType::PTR, ipp).unwrap();
980
981 let res = dns_req.query();
982
983 let elapsed = now.elapsed();
984 println!("Elapsed: {:.2?}", elapsed);
985
986 println!("{}", res);
987
988 assert_eq!(res.is_empty(), false);
989
990 let recs = res.collect_ok();
991 let rec = &recs[0];
992
993 assert_eq!(rec.server.as_str(), "/etc/hosts");
994 assert_eq!(rec.status, QDnsQueryRec::Ok);
995
996 assert_eq!(rec.resp.len(), 1);
997 assert_eq!(rec.resp[0].rdata, RecordPTR::wrap(RecordPTR{ fqdn: "localhost".to_string() }));
998 }
999
1000
1001 #[test]
1002 fn reverse_lookup_a()
1003 {
1004 use std::time::Instant;
1005
1006 let mut query_setup = QuerySetup::default();
1009 query_setup.set_measure_time(true);
1010
1011
1012 let res =
1013 QDns::make_a_aaaa_request(None, "dns.google", query_setup).unwrap();
1014
1015
1016 let now = Instant::now();
1017 let res = res.query();
1018
1019
1020 let elapsed = now.elapsed();
1021 println!("Elapsed: {:.2?}", elapsed);
1022
1023 println!("{}", res);
1027 }
1028
1029 #[cfg(feature = "enable_IDN_support")]
1030 #[test]
1031 fn reverse_lookup_a_idn()
1032 {
1033 use std::time::Instant;
1034
1035 let mut query_setup = QuerySetup::default();
1036 query_setup.set_measure_time(true);
1037
1038
1039 let res =
1040 QDns::make_a_aaaa_request(None, "законипорядок.бел", query_setup).unwrap();
1041
1042
1043 let now = Instant::now();
1044 let res = res.query();
1045
1046
1047 let elapsed = now.elapsed();
1048 println!("Elapsed: {:.2?}", elapsed);
1049
1050 println!("{}", res);
1051
1052 let ok: Vec<crate::QDnsQuery> = res.collect_ok_with_answers();
1053 let name = ok[0].get_responses()[0].name.clone();
1054 let idn_name = ok[0].get_responses()[0].get_full_domain_name_with_idn_decode().unwrap();
1055
1056 println!("{} -> {}", name, idn_name);
1057 assert_eq!(name, "xn--80aihfjcshcbin9q.xn--90ais");
1058
1059 assert_eq!(idn_name, "законипорядок.бел");
1060 }
1061
1062
1063 #[test]
1064 fn truncation_test_1()
1065 {
1066 use std::time::Instant;
1067
1068 let mut query_setup = QuerySetup::default();
1071 query_setup.set_measure_time(true);
1072
1073
1074 let mut res =
1075 QDns::make_empty(None, query_setup).unwrap();
1076
1077 res.add_request(QType::TXT, "truncate-zyxw11.go.dnscheck.tools").unwrap();
1078
1079
1080 let now = Instant::now();
1081 let res = res.query();
1082
1083
1084 let elapsed = now.elapsed();
1085 println!("Elapsed: {:.2?}", elapsed);
1086
1087 println!("{}", res);
1091 }
1092
1093 #[test]
1094 fn truncation_test_2()
1095 {
1096 use std::time::Instant;
1097
1098 let mut query_setup = QuerySetup::default();
1104 query_setup.set_measure_time(true);
1105
1106
1107 let mut res =
1108 QDns::make_empty(None, query_setup).unwrap();
1109
1110 res.add_request(QType::A, "dnscheck.tools").unwrap();
1111 res.add_request(QType::A, "localhost").unwrap();
1112 res.add_request(QType::A, "unknownnonexistentdomain.com").unwrap();
1113 res.add_request(QType::TXT, "example.com").unwrap();
1114
1115
1116 let now = Instant::now();
1117 let res = res.query();
1118
1119
1120 let elapsed = now.elapsed();
1121 println!("Elapsed: {:.2?}", elapsed);
1122
1123
1124 let (ans, errs) = res.collect_split();
1125
1126 println!("ANSWERS");
1127 for a in ans
1128 {
1129 println!("-----\n{} \n{}", a.0, a.1.unwrap());
1130 }
1131
1132 println!("ERRORS");
1133 for b in errs
1134 {
1135 println!("-----\n{} \n{}", b.0, b.1.err().unwrap());
1136 }
1137 }
1138}