Skip to content

Commit 173ebfd

Browse files
committed
Add time synchronization service
1 parent a70fa41 commit 173ebfd

File tree

20 files changed

+912
-72
lines changed

20 files changed

+912
-72
lines changed

landscape-common/src/config/api.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use serde::{Deserialize, Serialize};
22

3-
use crate::config::settings::{LandscapeDnsConfig, LandscapeMetricConfig, LandscapeUIConfig};
3+
use crate::config::settings::{
4+
LandscapeDnsConfig, LandscapeMetricConfig, LandscapeTimeConfig, LandscapeUIConfig,
5+
};
46

57
#[derive(Serialize, Debug, Clone)]
68
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
@@ -43,3 +45,17 @@ pub struct UpdateDnsConfigRequest {
4345
pub new_dns: LandscapeDnsConfig,
4446
pub expected_hash: String,
4547
}
48+
49+
#[derive(Serialize, Debug, Clone)]
50+
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
51+
pub struct GetTimeConfigResponse {
52+
pub time: LandscapeTimeConfig,
53+
pub hash: String,
54+
}
55+
56+
#[derive(Deserialize, Debug, Clone)]
57+
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
58+
pub struct UpdateTimeConfigRequest {
59+
pub new_time: LandscapeTimeConfig,
60+
pub expected_hash: String,
61+
}

landscape-common/src/config/loader.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use std::{
66
use crate::args::WebCommArgs;
77
use crate::config::runtime::{
88
AuthRuntimeConfig, DnsRuntimeConfig, LogRuntimeConfig, MetricRuntimeConfig, RuntimeConfig,
9-
StoreRuntimeConfig, WebRuntimeConfig,
9+
StoreRuntimeConfig, TimeRuntimeConfig, WebRuntimeConfig,
1010
};
1111
use crate::config::settings::LandscapeConfig;
1212
use crate::gateway::settings::GatewayRuntimeConfig;
1313
use crate::{
14+
DEFAULT_TIME_ENABLE, DEFAULT_TIME_SAMPLES_PER_SERVER, DEFAULT_TIME_SERVERS,
15+
DEFAULT_TIME_STEP_THRESHOLD_MS, DEFAULT_TIME_SYNC_INTERVAL_SECS, DEFAULT_TIME_TIMEOUT_SECS,
1416
LANDSCAPE_CONFIG_DIR_NAME, LANDSCAPE_LOG_DIR_NAME, LANDSCAPE_WEBROOT_DIR_NAME, LAND_CONFIG,
1517
};
1618

@@ -168,6 +170,27 @@ impl RuntimeConfig {
168170
.unwrap_or_else(|| "/dns-query".to_string()),
169171
};
170172

173+
let time = TimeRuntimeConfig {
174+
enabled: config.time.enabled.unwrap_or(DEFAULT_TIME_ENABLE),
175+
servers: config.time.servers.clone().unwrap_or_else(|| {
176+
DEFAULT_TIME_SERVERS.iter().map(|server| (*server).to_string()).collect()
177+
}),
178+
sync_interval_secs: config
179+
.time
180+
.sync_interval_secs
181+
.unwrap_or(DEFAULT_TIME_SYNC_INTERVAL_SECS),
182+
timeout_secs: config.time.timeout_secs.unwrap_or(DEFAULT_TIME_TIMEOUT_SECS),
183+
step_threshold_ms: config
184+
.time
185+
.step_threshold_ms
186+
.unwrap_or(DEFAULT_TIME_STEP_THRESHOLD_MS),
187+
samples_per_server: config
188+
.time
189+
.samples_per_server
190+
.unwrap_or(DEFAULT_TIME_SAMPLES_PER_SERVER)
191+
.max(1),
192+
};
193+
171194
let gateway = GatewayRuntimeConfig::from_file_config(&config.gateway);
172195

173196
RuntimeConfig {
@@ -179,6 +202,7 @@ impl RuntimeConfig {
179202
metric,
180203
dns,
181204
ui: config.ui.clone(),
205+
time,
182206
gateway,
183207
file_config: config,
184208
auto: args.auto,

landscape-common/src/config/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ pub mod runtime;
55
pub mod settings;
66

77
pub use api::{
8-
GetDnsConfigResponse, GetMetricConfigResponse, GetUIConfigResponse, UpdateDnsConfigRequest,
9-
UpdateMetricConfigRequest, UpdateUIConfigRequest,
8+
GetDnsConfigResponse, GetMetricConfigResponse, GetTimeConfigResponse, GetUIConfigResponse,
9+
UpdateDnsConfigRequest, UpdateMetricConfigRequest, UpdateTimeConfigRequest,
10+
UpdateUIConfigRequest,
1011
};
1112
pub use init::InitConfig;
1213
pub use runtime::{
1314
AuthRuntimeConfig, DnsRuntimeConfig, LogRuntimeConfig, MetricRuntimeConfig, RuntimeConfig,
14-
StoreRuntimeConfig, WebRuntimeConfig,
15+
StoreRuntimeConfig, TimeRuntimeConfig, WebRuntimeConfig,
1516
};
1617
pub use settings::{
1718
LandscapeAuthConfig, LandscapeConfig, LandscapeDnsConfig, LandscapeLogConfig,
18-
LandscapeMetricConfig, LandscapeStoreConfig, LandscapeUIConfig, LandscapeWebConfig,
19+
LandscapeMetricConfig, LandscapeStoreConfig, LandscapeTimeConfig, LandscapeUIConfig,
20+
LandscapeWebConfig,
1921
};
2022

2123
use uuid::Uuid;

landscape-common/src/config/runtime.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use std::{net::IpAddr, path::PathBuf};
22

33
use crate::config::settings::{
4-
LandscapeConfig, LandscapeDnsConfig, LandscapeMetricConfig, LandscapeUIConfig,
4+
LandscapeConfig, LandscapeDnsConfig, LandscapeMetricConfig, LandscapeTimeConfig,
5+
LandscapeUIConfig,
56
};
67
use crate::gateway::settings::GatewayRuntimeConfig;
7-
use crate::LANDSCAPE_DB_SQLITE_NAME;
8+
use crate::{
9+
DEFAULT_TIME_ENABLE, DEFAULT_TIME_SAMPLES_PER_SERVER, DEFAULT_TIME_SERVERS,
10+
DEFAULT_TIME_STEP_THRESHOLD_MS, DEFAULT_TIME_SYNC_INTERVAL_SECS, DEFAULT_TIME_TIMEOUT_SECS,
11+
LANDSCAPE_DB_SQLITE_NAME,
12+
};
813

914
#[derive(Clone, Debug)]
1015
pub struct RuntimeConfig {
@@ -17,6 +22,7 @@ pub struct RuntimeConfig {
1722
pub metric: MetricRuntimeConfig,
1823
pub dns: DnsRuntimeConfig,
1924
pub ui: LandscapeUIConfig,
25+
pub time: TimeRuntimeConfig,
2026
pub gateway: GatewayRuntimeConfig,
2127
pub auto: bool,
2228
}
@@ -75,6 +81,29 @@ pub struct DnsRuntimeConfig {
7581
pub doh_http_endpoint: String,
7682
}
7783

84+
#[derive(Clone, Debug)]
85+
pub struct TimeRuntimeConfig {
86+
pub enabled: bool,
87+
pub servers: Vec<String>,
88+
pub sync_interval_secs: u64,
89+
pub timeout_secs: u64,
90+
pub step_threshold_ms: u64,
91+
pub samples_per_server: u8,
92+
}
93+
94+
impl Default for TimeRuntimeConfig {
95+
fn default() -> Self {
96+
Self {
97+
enabled: DEFAULT_TIME_ENABLE,
98+
servers: DEFAULT_TIME_SERVERS.iter().map(|server| (*server).to_string()).collect(),
99+
sync_interval_secs: DEFAULT_TIME_SYNC_INTERVAL_SECS,
100+
timeout_secs: DEFAULT_TIME_TIMEOUT_SECS,
101+
step_threshold_ms: DEFAULT_TIME_STEP_THRESHOLD_MS,
102+
samples_per_server: DEFAULT_TIME_SAMPLES_PER_SERVER,
103+
}
104+
}
105+
}
106+
78107
impl RuntimeConfig {
79108
pub fn to_string_summary(&self) -> String {
80109
let address_http_str = match self.web.address {
@@ -120,8 +149,16 @@ impl RuntimeConfig {
120149
DB Max Threads: {}\n\
121150
Cleanup Interval: {}s\n\
122151
Cleanup Budget: {}ms\n\
123-
Cleanup Slice Window: {}s\n\
124-
Aggregate Interval: {}s\n",
152+
Cleanup Slice Window: {}s\n\
153+
Aggregate Interval: {}s\n\
154+
\n\
155+
[Time]\n\
156+
Enabled: {}\n\
157+
NTP Servers: {}\n\
158+
Sync Interval: {}s\n\
159+
Timeout: {}s\n\
160+
Step Threshold: {}ms\n\
161+
Samples Per Server: {}\n",
125162
self.home_path.display(),
126163
self.auth.admin_user,
127164
self.auth.admin_pass,
@@ -147,6 +184,12 @@ impl RuntimeConfig {
147184
self.metric.cleanup_time_budget_ms,
148185
self.metric.cleanup_slice_window_secs,
149186
self.metric.aggregate_interval_secs,
187+
self.time.enabled,
188+
self.time.servers.join(", "),
189+
self.time.sync_interval_secs,
190+
self.time.timeout_secs,
191+
self.time.step_threshold_ms,
192+
self.time.samples_per_server,
150193
)
151194
}
152195
}
@@ -218,6 +261,29 @@ impl DnsRuntimeConfig {
218261
}
219262
}
220263

264+
impl TimeRuntimeConfig {
265+
pub fn update_from_file_config(&mut self, config: &LandscapeTimeConfig) {
266+
if let Some(v) = config.enabled {
267+
self.enabled = v;
268+
}
269+
if let Some(v) = &config.servers {
270+
self.servers = v.clone();
271+
}
272+
if let Some(v) = config.sync_interval_secs {
273+
self.sync_interval_secs = v;
274+
}
275+
if let Some(v) = config.timeout_secs {
276+
self.timeout_secs = v;
277+
}
278+
if let Some(v) = config.step_threshold_ms {
279+
self.step_threshold_ms = v;
280+
}
281+
if let Some(v) = config.samples_per_server {
282+
self.samples_per_server = v;
283+
}
284+
}
285+
}
286+
221287
impl StoreRuntimeConfig {
222288
pub fn create_default_db_store(home_path: &PathBuf) -> String {
223289
let path = home_path.join(LANDSCAPE_DB_SQLITE_NAME);

landscape-common/src/config/settings.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,29 @@ pub struct LandscapeUIConfig {
127127
pub theme: Option<String>,
128128
}
129129

130+
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
131+
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
132+
pub struct LandscapeTimeConfig {
133+
#[serde(default, skip_serializing_if = "Option::is_none")]
134+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
135+
pub enabled: Option<bool>,
136+
#[serde(default, skip_serializing_if = "Option::is_none")]
137+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
138+
pub servers: Option<Vec<String>>,
139+
#[serde(default, skip_serializing_if = "Option::is_none")]
140+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
141+
pub sync_interval_secs: Option<u64>,
142+
#[serde(default, skip_serializing_if = "Option::is_none")]
143+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
144+
pub timeout_secs: Option<u64>,
145+
#[serde(default, skip_serializing_if = "Option::is_none")]
146+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
147+
pub step_threshold_ms: Option<u64>,
148+
#[serde(default, skip_serializing_if = "Option::is_none")]
149+
#[cfg_attr(feature = "openapi", schema(required = false, nullable = false))]
150+
pub samples_per_server: Option<u8>,
151+
}
152+
130153
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
131154
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
132155
pub struct LandscapeConfig {
@@ -153,5 +176,8 @@ pub struct LandscapeConfig {
153176
pub ui: LandscapeUIConfig,
154177
#[serde(default)]
155178
#[cfg_attr(feature = "openapi", schema(required = true))]
179+
pub time: LandscapeTimeConfig,
180+
#[serde(default)]
181+
#[cfg_attr(feature = "openapi", schema(required = true))]
156182
pub gateway: LandscapeGatewayConfig,
157183
}

landscape-common/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ pub const DEFAULT_DNS_CACHE_TTL: u32 = 24 * 60 * 60;
9999
pub const DEFAULT_DNS_NEGATIVE_CACHE_TTL: u32 = 120;
100100
pub const DEFAULT_DNS_DOH_LISTEN_PORT: u16 = 6053;
101101

102+
// --- Time Settings ---
103+
pub const DEFAULT_TIME_ENABLE: bool = false;
104+
pub const DEFAULT_TIME_SERVERS: &[&str] =
105+
&["ntp.aliyun.com:123", "time.cloudflare.com:123", "pool.ntp.org:123"];
106+
pub const DEFAULT_TIME_FALLBACK_SERVER: &str = "pool.ntp.org:123";
107+
pub const DEFAULT_TIME_SYNC_INTERVAL_SECS: u64 = 300;
108+
pub const DEFAULT_TIME_TIMEOUT_SECS: u64 = 3;
109+
pub const DEFAULT_TIME_STEP_THRESHOLD_MS: u64 = 500;
110+
pub const DEFAULT_TIME_SAMPLES_PER_SERVER: u8 = 3;
111+
102112
#[cfg(debug_assertions)]
103113
pub const DEFAULT_METRIC_CLEANUP_INTERVAL_SECS: u64 = 60;
104114
#[cfg(not(debug_assertions))]

landscape-common/src/sys_config/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
use crate::SYSCTL_IPV4_ARP_IGNORE_PATTERN;
2+
use std::time::{SystemTime, UNIX_EPOCH};
23

34
pub fn init_sysctl_setting() {
45
set_ipv4_arp_ignore_to_1();
56
}
67

8+
pub fn set_system_time(time: SystemTime) -> std::io::Result<()> {
9+
let duration = time.duration_since(UNIX_EPOCH).map_err(|_| {
10+
std::io::Error::new(std::io::ErrorKind::InvalidInput, "system time is before UNIX_EPOCH")
11+
})?;
12+
13+
let ts = libc::timespec {
14+
tv_sec: duration.as_secs() as libc::time_t,
15+
tv_nsec: duration.subsec_nanos() as libc::c_long,
16+
};
17+
18+
let result = unsafe { libc::clock_settime(libc::CLOCK_REALTIME, &ts) };
19+
if result == 0 {
20+
Ok(())
21+
} else {
22+
Err(std::io::Error::last_os_error())
23+
}
24+
}
25+
726
fn set_ipv4_arp_ignore_to_1() {
827
use sysctl::Sysctl;
928
if let Ok(ctl) = sysctl::Ctl::new(&SYSCTL_IPV4_ARP_IGNORE_PATTERN.replace("{}", "all")) {

0 commit comments

Comments
 (0)