feat(webnsupdate): add support for fritzbox style
All checks were successful
/ build (push) Successful in 26s
/ check (clippy) (push) Successful in 15s
/ check (module-ipv4-test) (push) Successful in 27s
/ check (module-ipv6-test) (push) Successful in 28s
/ check (module-nginx-test) (push) Successful in 28s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 6s
All checks were successful
/ build (push) Successful in 26s
/ check (clippy) (push) Successful in 15s
/ check (module-ipv4-test) (push) Successful in 27s
/ check (module-ipv6-test) (push) Successful in 28s
/ check (module-nginx-test) (push) Successful in 28s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 6s
Closes #80
This commit is contained in:
parent
26566fd612
commit
2d0aef57d7
1 changed files with 88 additions and 7 deletions
95
src/main.rs
95
src/main.rs
|
@ -5,7 +5,11 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum::{extract::State, routing::get, Router};
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use axum_client_ip::{SecureClientIp, SecureClientIpSource};
|
use axum_client_ip::{SecureClientIp, SecureClientIpSource};
|
||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
@ -384,7 +388,9 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create services
|
// 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
|
// if a password is provided, validate it
|
||||||
let app = if let Some(pass) = password_hash {
|
let app = if let Some(pass) = password_hash {
|
||||||
app.layer(auth::layer(Box::leak(pass), String::leak(salt)))
|
app.layer(auth::layer(Box::leak(pass), String::leak(salt)))
|
||||||
|
@ -410,6 +416,66 @@ fn main() -> Result<()> {
|
||||||
.wrap_err("failed to run main loop")
|
.wrap_err("failed to run main loop")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct FritzBoxUpdateParams {
|
||||||
|
/// The domain that should be updated
|
||||||
|
#[allow(unused)]
|
||||||
|
domain: Option<String>,
|
||||||
|
/// IPv4 address for the domain
|
||||||
|
ipv4: Option<Ipv4Addr>,
|
||||||
|
/// IPv6 address for the domain
|
||||||
|
ipv6: Option<Ipv6Addr>,
|
||||||
|
/// IPv6 prefix for the home network
|
||||||
|
#[allow(unused)]
|
||||||
|
ipv6prefix: Option<String>,
|
||||||
|
/// Whether the networks uses both IPv4 and IPv6
|
||||||
|
#[allow(unused)]
|
||||||
|
dualstack: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))]
|
||||||
|
async fn fritzbox_dyn_dns(
|
||||||
|
State(state): State<AppState<'static>>,
|
||||||
|
update_params: Query<FritzBoxUpdateParams>,
|
||||||
|
) -> 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"))]
|
#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))]
|
||||||
async fn update_records(
|
async fn update_records(
|
||||||
State(state): State<AppState<'static>>,
|
State(state): State<AppState<'static>>,
|
||||||
|
@ -418,11 +484,25 @@ async fn update_records(
|
||||||
info!("accepted update from {ip}");
|
info!("accepted update from {ip}");
|
||||||
|
|
||||||
if !state.ip_type.valid_for_type(ip) {
|
if !state.ip_type.valid_for_type(ip) {
|
||||||
let ip_type = state.ip_type;
|
tracing::warn!(
|
||||||
tracing::warn!("rejecting update from {ip} as we are running a {ip_type} filter");
|
"rejecting update from {ip} as we are running a {} filter",
|
||||||
return Err((StatusCode::CONFLICT, format!("running in {ip_type} mode")).into());
|
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 {
|
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let ips = {
|
let ips = {
|
||||||
|
@ -432,16 +512,17 @@ async fn update_records(
|
||||||
ips.clone()
|
ips.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ip_file = state.ip_file;
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
info!("updating last ips to {ips:?}");
|
info!("updating last ips to {ips:?}");
|
||||||
let data = serde_json::to_vec(&ips).expect("invalid serialization impl");
|
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}");
|
error!("Failed to update last IP: {err}");
|
||||||
}
|
}
|
||||||
info!("updated last ips to {ips:?}");
|
info!("updated last ips to {ips:?}");
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok("successful update")
|
Ok("Successfully updated IP of records!\n")
|
||||||
}
|
}
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
error!("nsupdate failed with code {status}");
|
error!("nsupdate failed with code {status}");
|
||||||
|
|
Loading…
Add table
Reference in a new issue