1#![deny(missing_docs)]
2#![deny(unsafe_code)]
3#![doc = tx5_core::__doc_header!()]
4use once_cell::sync::Lazy;
9use trust_dns_resolver::config::*;
10use trust_dns_resolver::error::*;
11use trust_dns_resolver::*;
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum OnlineEvent {
16 Online,
18
19 Offline,
21}
22
23impl std::fmt::Display for OnlineEvent {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{self:?}")
26 }
27}
28
29#[derive(Clone)]
31pub struct OnlineReceiver(tokio::sync::watch::Receiver<OnlineEvent>);
32
33impl Default for OnlineReceiver {
34 fn default() -> Self {
35 RCV.clone()
36 }
37}
38
39impl OnlineReceiver {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn status(&mut self) -> OnlineEvent {
47 while self.0.has_changed().unwrap() {
49 self.0.borrow_and_update();
50 }
51 *self.0.borrow_and_update()
52 }
53
54 pub async fn recv(&mut self) -> OnlineEvent {
56 let _ = self.0.changed().await;
58 *self.0.borrow_and_update()
59 }
60}
61
62static RCV: Lazy<OnlineReceiver> = Lazy::new(|| {
63 let (snd, rcv) = tokio::sync::watch::channel(OnlineEvent::Offline);
64
65 tokio::task::spawn(async move {
66 loop {
67 let status = check_status().await;
68
69 snd.send_if_modified(move |s| {
70 if *s == status {
71 false
72 } else {
73 *s = status;
74 true
75 }
76 });
77
78 let s = rand::Rng::random_range(&mut rand::rng(), 4.0..8.0);
79 let s = std::time::Duration::from_secs_f64(s);
80 tokio::time::sleep(s).await;
81 }
82 });
83
84 OnlineReceiver(rcv)
85});
86
87async fn check_status() -> OnlineEvent {
88 let mut servers = Vec::new();
89 servers.append(&mut NameServerConfigGroup::cloudflare().into_inner());
90 servers.append(&mut NameServerConfigGroup::google().into_inner());
91 servers.append(&mut NameServerConfigGroup::quad9().into_inner());
92
93 rand::seq::SliceRandom::shuffle(&mut servers[..], &mut rand::rng());
94
95 let conf = ResolverConfig::from_parts(None, Vec::new(), servers);
96
97 let mut opts = ResolverOpts::default();
98 opts.server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder;
99 opts.cache_size = 0;
100 opts.use_hosts_file = false;
101
102 let r = TokioAsyncResolver::tokio(conf, opts);
103
104 const TLD: [&str; 5] = [".com.", ".net.", ".org.", ".edu.", ".gov."];
105
106 let tld =
107 rand::seq::IndexedRandom::choose(&TLD[..], &mut rand::rng()).unwrap();
108
109 let mut nonce = [0_u8; 8];
110 rand::Rng::fill(&mut rand::rng(), &mut nonce);
111 let mut name = String::new();
112 for c in nonce {
113 name.push_str(&format!("{c:02x}"));
114 }
115 name.push_str(tld);
116
117 r.clear_cache();
118
119 match r.lookup_ip(name).await {
120 Ok(_) => OnlineEvent::Online,
121 Err(err) => match err.kind() {
122 ResolveErrorKind::NoRecordsFound { .. } => OnlineEvent::Online,
123 _ => OnlineEvent::Offline,
124 },
125 }
126}