From 2d0aef57d700a50eeb4111fc2f0660e41e4945e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?= Date: Mon, 27 Jan 2025 18:49:46 +0100 Subject: [PATCH] feat(webnsupdate): add support for fritzbox style Closes #80 --- src/main.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 922b00f..028eb88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,11 @@ use std::{ time::Duration, }; -use axum::{extract::State, routing::get, Router}; +use axum::{ + extract::{Query, State}, + routing::get, + Router, +}; use axum_client_ip::{SecureClientIp, SecureClientIpSource}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use clap::{Parser, Subcommand}; @@ -384,7 +388,9 @@ fn main() -> Result<()> { } // Create services - let app = Router::new().route("/update", get(update_records)); + let app = Router::new() + .route("/update", get(update_records)) + .route("/fritzbox-dyn-dns", get(fritzbox_dyn_dns)); // if a password is provided, validate it let app = if let Some(pass) = password_hash { app.layer(auth::layer(Box::leak(pass), String::leak(salt))) @@ -410,6 +416,66 @@ fn main() -> Result<()> { .wrap_err("failed to run main loop") } +#[derive(Debug, serde::Deserialize)] +struct FritzBoxUpdateParams { + /// The domain that should be updated + #[allow(unused)] + domain: Option, + /// IPv4 address for the domain + ipv4: Option, + /// IPv6 address for the domain + ipv6: Option, + /// IPv6 prefix for the home network + #[allow(unused)] + ipv6prefix: Option, + /// Whether the networks uses both IPv4 and IPv6 + #[allow(unused)] + dualstack: Option, +} + +#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))] +async fn fritzbox_dyn_dns( + State(state): State>, + update_params: Query, +) -> axum::response::Result<&'static str> { + info!("received params: {update_params:#?}"); + let FritzBoxUpdateParams { + domain: _, + ipv4, + ipv6, + ipv6prefix: _, + dualstack: _, + } = update_params.0; + + if ipv4.is_none() && ipv6.is_none() { + return Err(( + StatusCode::BAD_REQUEST, + "failed to provide an IP for the update", + ) + .into()); + } + + if let Some(ip) = ipv4 { + let ip = IpAddr::V4(ip); + if !state.ip_type.valid_for_type(ip) { + tracing::warn!("requested update of IPv4 but we are {}", state.ip_type); + } + + _ = trigger_update(ip, &state).await?; + } + + if let Some(ip) = ipv6 { + let ip = IpAddr::V6(ip); + if !state.ip_type.valid_for_type(ip) { + tracing::warn!("requested update of IPv6 but we are {}", state.ip_type); + } + + _ = trigger_update(ip, &state).await?; + } + + Ok("Successfully updated IP of records!\n") +} + #[tracing::instrument(skip(state), level = "trace", ret(level = "info"))] async fn update_records( State(state): State>, @@ -418,11 +484,25 @@ async fn update_records( info!("accepted update from {ip}"); if !state.ip_type.valid_for_type(ip) { - let ip_type = state.ip_type; - tracing::warn!("rejecting update from {ip} as we are running a {ip_type} filter"); - return Err((StatusCode::CONFLICT, format!("running in {ip_type} mode")).into()); + tracing::warn!( + "rejecting update from {ip} as we are running a {} filter", + state.ip_type + ); + return Err(( + StatusCode::CONFLICT, + format!("running in {} mode", state.ip_type), + ) + .into()); } + trigger_update(ip, &state).await +} + +#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))] +async fn trigger_update( + ip: IpAddr, + state: &AppState<'static>, +) -> axum::response::Result<&'static str> { match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await { Ok(status) if status.success() => { let ips = { @@ -432,16 +512,17 @@ async fn update_records( ips.clone() }; + let ip_file = state.ip_file; tokio::task::spawn_blocking(move || { 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) { + if let Err(err) = std::fs::write(ip_file, data) { error!("Failed to update last IP: {err}"); } info!("updated last ips to {ips:?}"); }); - Ok("successful update") + Ok("Successfully updated IP of records!\n") } Ok(status) => { error!("nsupdate failed with code {status}");