230 lines
6.6 KiB
Rust
230 lines
6.6 KiB
Rust
extern crate anyhow;
|
|
extern crate clokwerk;
|
|
extern crate lettre;
|
|
extern crate lettre_email;
|
|
extern crate log;
|
|
extern crate native_tls;
|
|
extern crate serde;
|
|
extern crate simplelog;
|
|
extern crate tokio;
|
|
extern crate toml;
|
|
extern crate trust_dns_resolver;
|
|
|
|
use self::native_tls::{Protocol, TlsConnector};
|
|
use anyhow::{bail, Result};
|
|
use clokwerk::{Scheduler, TimeUnits};
|
|
use lettre::smtp::authentication::Credentials;
|
|
use lettre::{ClientSecurity, ClientTlsParameters, SmtpClient, Transport};
|
|
use lettre_email::EmailBuilder;
|
|
use log::{error, info};
|
|
use query_external_ip::Consensus;
|
|
use serde::Deserialize;
|
|
use simplelog::{ConfigBuilder, LevelFilter, WriteLogger};
|
|
use std::fs::{self, OpenOptions};
|
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
use std::string::String;
|
|
use std::{thread, time::Duration};
|
|
use tokio::runtime::{Builder, Runtime};
|
|
use trust_dns_resolver::{config::*, Name, Resolver};
|
|
|
|
fn main() -> Result<()> {
|
|
// Init log
|
|
let logfile = OpenOptions::new()
|
|
.append(true)
|
|
.create(true)
|
|
.open("log.txt")
|
|
.unwrap();
|
|
WriteLogger::init(
|
|
LevelFilter::Info,
|
|
ConfigBuilder::new()
|
|
.set_time_format_str("%d/%m/%Y %H:%M:%S")
|
|
.set_time_to_local(true)
|
|
.build(),
|
|
logfile,
|
|
)
|
|
.unwrap();
|
|
|
|
// Init tester
|
|
info!("Init tester");
|
|
let tester = match Tester::new() {
|
|
Err(e) => {
|
|
error!("Error {}", e);
|
|
bail!("Cannot init tester");
|
|
}
|
|
Ok(tester) => tester,
|
|
};
|
|
|
|
// Initial check
|
|
if let Err(e) = tester.check() {
|
|
error!("Error {}", e);
|
|
bail!("Cannot get initial IP");
|
|
}
|
|
|
|
if let Some(false) = tester.config.test {
|
|
// Init scheduler
|
|
info!("Init scheduler");
|
|
let mut scheduler = Scheduler::new();
|
|
scheduler.every(1.day()).at("06:00").run(move || {
|
|
if let Err(e) = tester.check() {
|
|
error!("Error {}", e);
|
|
}
|
|
});
|
|
|
|
// Run
|
|
loop {
|
|
scheduler.run_pending();
|
|
thread::sleep(Duration::from_secs(60));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
struct Tester {
|
|
config: Config,
|
|
dns_resolver: Resolver,
|
|
async_runtime: Runtime,
|
|
}
|
|
|
|
impl Tester {
|
|
fn new() -> Result<Tester> {
|
|
let mut resolver_opt = ResolverOpts::default();
|
|
resolver_opt.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
|
Ok(Tester {
|
|
config: toml::from_str(&fs::read_to_string("config.toml")?)?,
|
|
dns_resolver: Resolver::from_system_conf()?,
|
|
async_runtime: Builder::new_multi_thread().enable_all().build().unwrap(),
|
|
})
|
|
}
|
|
|
|
fn check(&self) -> Result<()> {
|
|
info!("Check");
|
|
match self.get_ip() {
|
|
Ok((ipv4, ipv6)) => {
|
|
let msg = self.test_domains(ipv4, ipv6)?;
|
|
if !msg.is_empty() {
|
|
self.send_mail("IP check error".to_owned(), msg)?;
|
|
}
|
|
}
|
|
Err(err) => {
|
|
let msg = format!("Cannot get IP ({})", err);
|
|
error!("{}", msg);
|
|
self.send_mail("Error on getting IP".to_owned(), msg)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_ip(&self) -> Result<(Ipv4Addr, Ipv6Addr)> {
|
|
let cons = self.async_runtime.block_on(Consensus::get())?;
|
|
let ipv4 = cons.v4().unwrap();
|
|
let ipv6 = cons.v6().unwrap();
|
|
info!("Current IPv4 is {} and IPv6 {}", ipv4, ipv6);
|
|
|
|
Ok((ipv4, ipv6))
|
|
}
|
|
|
|
fn test_domains(&self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Result<String> {
|
|
macro_rules! msg {
|
|
($full_msg:expr, $($arg:tt)*) => {{
|
|
let msg = format!($($arg)*);
|
|
info!("{}", msg);
|
|
$full_msg.push_str(&msg);
|
|
$full_msg.push('\n');
|
|
}}
|
|
}
|
|
|
|
let mut full_msg = String::new();
|
|
for domain in self.config.server.domains.iter() {
|
|
if let Ok(dns_ipv4_lookup) = self.dns_resolver.ipv4_lookup(domain.clone()) {
|
|
let dns_ipv4 = dns_ipv4_lookup.iter().next().unwrap();
|
|
if *dns_ipv4 != ipv4 {
|
|
msg!(
|
|
full_msg,
|
|
"Wrong IPV4 for {} (DNS: {}, current: {})",
|
|
domain,
|
|
dns_ipv4,
|
|
ipv4
|
|
);
|
|
}
|
|
} else {
|
|
msg!(full_msg, "Cannot get IPv4 address for {}", domain);
|
|
}
|
|
if let Ok(dns_ipv6_lookup) = self.dns_resolver.ipv6_lookup(domain.clone()) {
|
|
let dns_ipv6 = dns_ipv6_lookup.iter().next().unwrap();
|
|
if *dns_ipv6 != ipv6 {
|
|
msg!(
|
|
full_msg,
|
|
"Wrong IPV6 for {} (DNS: {}, current: {})",
|
|
domain,
|
|
dns_ipv6,
|
|
ipv6
|
|
);
|
|
}
|
|
} else {
|
|
msg!(full_msg, "Cannot get IPv6 address for {}", domain);
|
|
}
|
|
}
|
|
Ok(full_msg)
|
|
}
|
|
|
|
fn send_mail(&self, subject: String, body: String) -> Result<()> {
|
|
info!("Send mail {}", subject);
|
|
|
|
if let Some(false) = self.config.test {
|
|
let email = EmailBuilder::new()
|
|
.to(self.config.mail.from.clone())
|
|
.from(self.config.mail.to.clone())
|
|
.subject(subject)
|
|
.text(body)
|
|
.build()?;
|
|
|
|
// Create transport
|
|
let creds = Credentials::new(
|
|
self.config.mail.username.clone(),
|
|
self.config.mail.password.clone(),
|
|
);
|
|
let mut tls_builder = TlsConnector::builder();
|
|
tls_builder.min_protocol_version(Some(Protocol::Sslv3));
|
|
let tls_parameters = ClientTlsParameters::new(
|
|
self.config.mail.server.clone(),
|
|
tls_builder.build().unwrap(),
|
|
);
|
|
let mut mailer = SmtpClient::new(
|
|
(self.config.mail.server.clone(), self.config.mail.port),
|
|
ClientSecurity::Required(tls_parameters),
|
|
)?
|
|
.credentials(creds)
|
|
.transport();
|
|
|
|
mailer.send(email.into())?;
|
|
} else {
|
|
println!("subject: {} - body: {}", subject, body);
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Config {
|
|
mail: MailConfig,
|
|
server: ServerConfig,
|
|
test: Option<bool>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ServerConfig {
|
|
domains: Vec<Name>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct MailConfig {
|
|
server: String,
|
|
port: u16,
|
|
username: String,
|
|
password: String,
|
|
from: (String, String),
|
|
to: String,
|
|
}
|