aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 86195550840e4c48bef78c90fcf8ec98254bbb49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::io;
use std::net::{self, IpAddr, SocketAddr};
use std::num;
use std::path::Path;
use std::thread;
use std::time::{self, Duration, SystemTime};

use tokio::fs;
use tokio::signal::unix::{signal, SignalKind};

use chrono::DateTime;
use nix::sys::time::TimeSpec;
use nix::time::ClockId;
use thiserror::Error;
use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts};
use trust_dns_resolver::Resolver;

const EPOCH_OFFSET: i64 = 2208988800;
const NTP_SERVER: &str = "2.pool.ntp.org";
const NTP_PORT: u16 = 123;
const DNS_SERVER: &str = "[2620:fe::fe]:53";
const INTERVAL: Duration = Duration::from_secs(3600);

#[derive(Debug, Error)]
enum Error {
    #[error("can't find ntp server hostname")]
    NoHostname,

    #[error("io: {0}")]
    Io(#[from] io::Error),
    #[error("can't parse network address: {0}")]
    ParseAddr(#[from] net::AddrParseError),
    #[error("system time monotonicity error: {0}")]
    SystemTime(#[from] time::SystemTimeError),
    #[error("integer doesn't fit: {0}")]
    TryFromInt(#[from] num::TryFromIntError),

    #[error("chrono parse: {0}")]
    ChronoParse(#[from] chrono::ParseError),
    #[error("nix errno: {0}")]
    NixErrno(#[from] nix::errno::Errno),
    #[error("ntp: {0}")]
    Ntp(#[from] ntp::errors::Error),
    #[error("trust_dns_resolver resolve error: {0}")]
    TrustDnsResolve(#[from] trust_dns_resolver::error::ResolveError),
}

type Result<T> = std::result::Result<T, Error>;

#[tokio::main]
async fn main() -> Result<()> {
    let ds_config = Path::new(rsdsl_ip_config::LOCATION);
    while !ds_config.exists() {
        println!("wait for pppoe");
        thread::sleep(Duration::from_secs(8));
    }

    let mut resync = tokio::time::interval(INTERVAL);
    let mut sigterm = signal(SignalKind::terminate())?;

    loop {
        tokio::select! {
            _ = resync.tick() => match sync_time(NTP_SERVER).await {
                Ok(_) => {}
                Err(e) => eprintln!("can't synchronize system time: {}", e),
            },
            _ = sigterm.recv() => {
                sysnow_to_disk().await?;

                println!("save system time");
                return Ok(());
            }
        }
    }
}

async fn last_time_unix() -> Option<i64> {
    Some(i64::from_be_bytes(
        fs::read("/data/ntp.last_unix").await.ok()?[..8]
            .try_into()
            .ok()?,
    ))
}

async fn sysnow_to_disk() -> Result<()> {
    let t: i64 = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_secs()
        .try_into()?;
    fs::write("/data/ntp.last_unix", t.to_be_bytes()).await?;

    Ok(())
}

async fn sync_time(server: &str) -> Result<()> {
    let last = last_time_unix()
        .await
        .unwrap_or(DateTime::parse_from_rfc3339(env!("SOURCE_TIMESTAMP"))?.timestamp());

    let dns = DNS_SERVER.parse()?;
    let server_resolved = SocketAddr::new(resolve_custom_dns(server, dns)?, NTP_PORT);

    let time = ntp::request(server_resolved)?.transmit_time;

    let mut t = time.sec as i64 - EPOCH_OFFSET;
    while t < last {
        t += 2_i64.pow(32); // NTP era duration.
    }

    let timespec = TimeSpec::new(t, 0);
    nix::time::clock_settime(ClockId::CLOCK_REALTIME, timespec)?;

    fs::write("/data/ntp.last_unix", t.to_be_bytes()).await?;

    println!("set system time");
    Ok(())
}

fn resolve_custom_dns(hostname: &str, custom_dns: SocketAddr) -> Result<IpAddr> {
    let mut cfg = ResolverConfig::new();

    cfg.add_name_server(NameServerConfig::new(custom_dns, Protocol::Udp));

    let resolver = Resolver::new(cfg, ResolverOpts::default())?;
    let response = resolver.lookup_ip(hostname)?;

    let ip_addr = response.iter().next().ok_or(Error::NoHostname)?;
    Ok(ip_addr)
}