Compare commits
2 commits
5f961ac785
...
71760a59a7
Author | SHA1 | Date | |
---|---|---|---|
71760a59a7 | |||
a2735b46b5 |
4 changed files with 98 additions and 46 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -78,9 +78,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efea76243612a2436fb4074ba0cf3ba9ea29efdeb72645d8fc63f116462be1de"
|
||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
|
@ -123,12 +123,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab1b0df7cded837c40dacaa2e1c33aa17c84fc3356ae67b5645f1e83190753e"
|
||||
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
|
@ -728,9 +728,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.43"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
@ -1086,9 +1086,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
|
@ -1139,6 +1139,8 @@ dependencies = [
|
|||
"insta",
|
||||
"miette",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
|
|
|
@ -27,6 +27,8 @@ clap-verbosity-flag = { version = "3", default-features = false, features = [
|
|||
http = "1"
|
||||
miette = { version = "7", features = ["fancy"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0.137"
|
||||
tokio = { version = "1", features = ["macros", "rt", "process", "io-util"] }
|
||||
tower-http = { version = "0.6.2", features = ["validate-request"] }
|
||||
tracing = "0.1"
|
||||
|
|
12
flake.lock
generated
12
flake.lock
generated
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1737250794,
|
||||
"narHash": "sha256-bdIPhvsAKyYQzqAIeay4kOxTHGwLGkhM+IlBIsmMYFI=",
|
||||
"lastModified": 1737563566,
|
||||
"narHash": "sha256-GLJvkOG29XCynQm8XWPyykMRqIhxKcBARVu7Ydrz02M=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "c5b7075f4a6d523fe8204618aa9754e56478c0e0",
|
||||
"rev": "849376434956794ebc7a6b487d31aace395392ba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -37,11 +37,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1737062831,
|
||||
"narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=",
|
||||
"lastModified": 1737469691,
|
||||
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c",
|
||||
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
110
src/main.rs
110
src/main.rs
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
io::ErrorKind,
|
||||
net::{IpAddr, SocketAddr},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
@ -114,6 +114,48 @@ struct AppState<'a> {
|
|||
|
||||
/// The file where the last IP is stored
|
||||
ip_file: &'a Path,
|
||||
|
||||
/// Last recorded IPs
|
||||
last_ips: std::sync::Arc<tokio::sync::Mutex<SavedIPs>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct SavedIPs {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ipv4: Option<Ipv4Addr>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ipv6: Option<Ipv6Addr>,
|
||||
}
|
||||
|
||||
impl SavedIPs {
|
||||
fn update(&mut self, ip: IpAddr) {
|
||||
match ip {
|
||||
IpAddr::V4(ipv4_addr) => self.ipv4 = Some(ipv4_addr),
|
||||
IpAddr::V6(ipv6_addr) => self.ipv6 = Some(ipv6_addr),
|
||||
}
|
||||
}
|
||||
|
||||
fn ips(&self) -> impl Iterator<Item = IpAddr> {
|
||||
self.ipv4
|
||||
.map(IpAddr::V4)
|
||||
.into_iter()
|
||||
.chain(self.ipv6.map(IpAddr::V6))
|
||||
}
|
||||
|
||||
fn from_str(data: &str) -> miette::Result<Self> {
|
||||
match data.parse::<IpAddr>() {
|
||||
// Old format
|
||||
Ok(IpAddr::V4(ipv4)) => Ok(Self {
|
||||
ipv4: Some(ipv4),
|
||||
ipv6: None,
|
||||
}),
|
||||
Ok(IpAddr::V6(ipv6)) => Ok(Self {
|
||||
ipv4: None,
|
||||
ipv6: Some(ipv6),
|
||||
}),
|
||||
Err(_) => serde_json::from_str(data).into_diagnostic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState<'static> {
|
||||
|
@ -137,7 +179,7 @@ impl AppState<'static> {
|
|||
let ttl = Duration::from_secs(*ttl);
|
||||
|
||||
// Use last registered IP address if available
|
||||
let ip_file = data_dir.join("last-ip");
|
||||
let ip_file = Box::leak(data_dir.join("last-ip").into_boxed_path());
|
||||
|
||||
let state = AppState {
|
||||
ttl,
|
||||
|
@ -155,7 +197,10 @@ impl AppState<'static> {
|
|||
Ok(&*Box::leak(path.into()))
|
||||
})
|
||||
.transpose()?,
|
||||
ip_file: Box::leak(ip_file.into_boxed_path()),
|
||||
ip_file,
|
||||
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
|
||||
load_ip(ip_file)?.unwrap_or_default(),
|
||||
)),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
|
@ -167,7 +212,7 @@ impl AppState<'static> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
|
||||
fn load_ip(path: &Path) -> Result<Option<SavedIPs>> {
|
||||
debug!("loading last IP from {}", path.display());
|
||||
let data = match std::fs::read_to_string(path) {
|
||||
Ok(ip) => ip,
|
||||
|
@ -181,11 +226,9 @@ fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
|
|||
}
|
||||
};
|
||||
|
||||
Ok(Some(
|
||||
data.parse()
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to parse last ip address")?,
|
||||
))
|
||||
SavedIPs::from_str(&data)
|
||||
.wrap_err_with(|| format!("failed to load last ip address from {}", path.display()))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
#[tracing::instrument(err)]
|
||||
|
@ -266,28 +309,24 @@ fn main() -> Result<()> {
|
|||
.wrap_err("failed to start the tokio runtime")?;
|
||||
|
||||
rt.block_on(async {
|
||||
// Load previous IP and update DNS record to point to it (if available)
|
||||
match load_ip(state.ip_file) {
|
||||
Ok(Some(ip)) => {
|
||||
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
error!("nsupdate failed: code {status}");
|
||||
bail!("nsupdate returned with code {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to update records with previous IP: {err}");
|
||||
return Err(err)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to update records with previous IP");
|
||||
// Update DNS record with previous IPs (if available)
|
||||
let ips = state.last_ips.lock().await.clone();
|
||||
for ip in ips.ips() {
|
||||
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
error!("nsupdate failed: code {status}");
|
||||
bail!("nsupdate returned with code {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to update records with previous IP: {err}");
|
||||
return Err(err)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to update records with previous IP");
|
||||
}
|
||||
}
|
||||
Ok(None) => info!("No previous IP address set"),
|
||||
|
||||
Err(err) => error!("Ignoring previous IP due to: {err}"),
|
||||
};
|
||||
}
|
||||
|
||||
// Create services
|
||||
let app = Router::new().route("/update", get(update_records));
|
||||
|
@ -324,13 +363,22 @@ async fn update_records(
|
|||
info!("accepted update from {ip}");
|
||||
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||
Ok(status) if status.success() => {
|
||||
let ips = {
|
||||
// Update state
|
||||
let mut ips = state.last_ips.lock().await;
|
||||
ips.update(ip);
|
||||
ips.clone()
|
||||
};
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
info!("updating last ip to {ip}");
|
||||
if let Err(err) = std::fs::write(state.ip_file, format!("{ip}")) {
|
||||
info!("updating last ips to {ips:?}");
|
||||
let data = serde_json::to_vec(&ips).expect("invalid serialization impl");
|
||||
if let Err(err) = std::fs::write(state.ip_file, data) {
|
||||
error!("Failed to update last IP: {err}");
|
||||
}
|
||||
info!("updated last ip to {ip}");
|
||||
info!("updated last ips to {ips:?}");
|
||||
});
|
||||
|
||||
Ok("successful update")
|
||||
}
|
||||
Ok(status) => {
|
||||
|
|
Loading…
Add table
Reference in a new issue