Compare commits
5 Commits
5466090256
...
v1.0
Author | SHA1 | Date | |
---|---|---|---|
787f74e3ec | |||
9b68b32354 | |||
e5c9cb6024 | |||
0cd2d364aa | |||
f34c4609a5 |
43
src/exit_listener.rs
Normal file
43
src/exit_listener.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tokio::{
|
||||||
|
self, signal,
|
||||||
|
sync::{futures::Notified, Notify},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ExitListener {
|
||||||
|
should_exit: Arc<AtomicBool>,
|
||||||
|
notify: Arc<Notify>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExitListener {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let this = Self {
|
||||||
|
should_exit: Arc::new(AtomicBool::new(false)),
|
||||||
|
notify: Arc::new(Notify::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let should_exit = this.should_exit.clone();
|
||||||
|
let notify = this.notify.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("Failed to install CTRL+C signal handler");
|
||||||
|
should_exit.store(true, Ordering::SeqCst);
|
||||||
|
notify.notify_one();
|
||||||
|
});
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notified(&self) -> Notified<'_> {
|
||||||
|
self.notify.notified()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_exit(&self) -> bool {
|
||||||
|
self.should_exit.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
}
|
@ -3,40 +3,14 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{self, Deserialize, Serialize};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
|
||||||
fmt,
|
fmt,
|
||||||
net::{IpAddr, Ipv4Addr},
|
net::{IpAddr, Ipv4Addr},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::cloudflare_responses::{CloudflareResponse, DnsRecord};
|
||||||
use crate::get_current_public_ipv4;
|
use crate::get_current_public_ipv4;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
struct DnsRecord {
|
|
||||||
id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
record_type: Box<str>,
|
|
||||||
name: Box<str>,
|
|
||||||
content: Box<str>,
|
|
||||||
ttl: u32,
|
|
||||||
proxied: bool,
|
|
||||||
locked: bool,
|
|
||||||
zone_id: Box<str>,
|
|
||||||
zone_name: Box<str>,
|
|
||||||
modified_on: Box<str>,
|
|
||||||
created_on: Box<str>,
|
|
||||||
meta: HashMap<Box<str>, serde_json::Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct CloudflareResponse {
|
|
||||||
success: bool,
|
|
||||||
errors: Vec<HashMap<String, serde_json::Value>>,
|
|
||||||
messages: Vec<HashMap<String, serde_json::Value>>,
|
|
||||||
result: Option<Vec<DnsRecord>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CloudflareClient<A, Z>
|
pub struct CloudflareClient<A, Z>
|
||||||
where
|
where
|
||||||
A: fmt::Display,
|
A: fmt::Display,
|
27
src/internet/cloudflare_responses.rs
Normal file
27
src/internet/cloudflare_responses.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde;
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub struct DnsRecord {
|
||||||
|
pub id: Box<str>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub record_type: Box<str>,
|
||||||
|
pub name: Box<str>,
|
||||||
|
pub content: Box<str>,
|
||||||
|
pub ttl: u32,
|
||||||
|
pub proxied: bool,
|
||||||
|
pub locked: bool,
|
||||||
|
pub zone_id: Box<str>,
|
||||||
|
pub zone_name: Box<str>,
|
||||||
|
pub modified_on: Box<str>,
|
||||||
|
pub created_on: Box<str>,
|
||||||
|
pub meta: HashMap<Box<str>, serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
|
pub struct CloudflareResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub errors: Box<[HashMap<Box<str>, serde_json::Value>]>,
|
||||||
|
pub messages: Box<[HashMap<Box<str>, serde_json::Value>]>,
|
||||||
|
pub result: Option<Vec<DnsRecord>>,
|
||||||
|
}
|
3
src/internet/mod.rs
Normal file
3
src/internet/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod cloudflare;
|
||||||
|
pub mod cloudflare_responses;
|
||||||
|
pub use cloudflare::CloudflareClient;
|
11
src/lib.rs
11
src/lib.rs
@ -1,17 +1,22 @@
|
|||||||
// SPDX: BSD-2-Clause
|
// SPDX: BSD-2-Clause
|
||||||
|
|
||||||
mod cloudflare;
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod exit_listener;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod message_handler;
|
mod message_handler;
|
||||||
|
mod network_change_listener;
|
||||||
mod public_ip;
|
mod public_ip;
|
||||||
mod tests;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use cloudflare::CloudflareClient;
|
mod tests;
|
||||||
|
mod internet;
|
||||||
|
|
||||||
|
pub use internet::CloudflareClient;
|
||||||
pub use config::{get_config_path, read_config, Config};
|
pub use config::{get_config_path, read_config, Config};
|
||||||
|
pub use exit_listener::ExitListener;
|
||||||
pub use logging::init_logger;
|
pub use logging::init_logger;
|
||||||
pub use message_handler::MessageHandler;
|
pub use message_handler::MessageHandler;
|
||||||
|
pub use network_change_listener::NetworkChangeListener;
|
||||||
pub use public_ip::get_current_public_ipv4;
|
pub use public_ip::get_current_public_ipv4;
|
||||||
|
|
||||||
pub const PROGRAM_NAME: &'static str = "dynip-cloudflare";
|
pub const PROGRAM_NAME: &'static str = "dynip-cloudflare";
|
||||||
|
77
src/main.rs
77
src/main.rs
@ -1,36 +1,14 @@
|
|||||||
// SPDX: BSD-2-Clause
|
// SPDX: BSD-2-Clause
|
||||||
|
|
||||||
use futures::future::{self, Either};
|
use futures::future::{self, Either};
|
||||||
use std::sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use netlink_sys::{AsyncSocket, SocketAddr};
|
use scopeguard::defer;
|
||||||
use rtnetlink::new_connection;
|
use tokio::time;
|
||||||
|
|
||||||
use dynip_cloudflare::{
|
use dynip_cloudflare::{
|
||||||
utils::{self, duration_to_string},
|
utils, CloudflareClient, ExitListener, MessageHandler, NetworkChangeListener,
|
||||||
CloudflareClient, MessageHandler, MAX_ERORS_IN_ROW_DEFAULT,
|
MAX_ERORS_IN_ROW_DEFAULT,
|
||||||
};
|
};
|
||||||
use scopeguard::defer;
|
|
||||||
use tokio::{signal, sync::Notify, time};
|
|
||||||
|
|
||||||
const RTNLGRP_LINK: u32 = 1;
|
|
||||||
const RTNLGRP_IPV4_IFADDR: u32 = 5;
|
|
||||||
|
|
||||||
const fn nl_mgrp(group: u32) -> u32 {
|
|
||||||
if group > 31 {
|
|
||||||
panic!("use netlink_sys::Socket::add_membership() for this group");
|
|
||||||
}
|
|
||||||
if group == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1 << (group - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@ -38,24 +16,11 @@ async fn main() {
|
|||||||
defer! {
|
defer! {
|
||||||
log::logger().flush();
|
log::logger().flush();
|
||||||
}
|
}
|
||||||
let should_exit = Arc::new(AtomicBool::new(false));
|
let exit_listener = ExitListener::new();
|
||||||
let notify = Arc::new(Notify::new());
|
|
||||||
|
|
||||||
let should_exit_clone = should_exit.clone();
|
let config = match utils::get_config().await {
|
||||||
let notify_clone = notify.clone();
|
Some(aux) => aux,
|
||||||
|
None => return,
|
||||||
tokio::spawn(async move {
|
|
||||||
signal::ctrl_c()
|
|
||||||
.await
|
|
||||||
.expect("Failed to install CTRL+C signal handler");
|
|
||||||
should_exit_clone.store(true, Ordering::SeqCst);
|
|
||||||
notify_clone.notify_one();
|
|
||||||
});
|
|
||||||
|
|
||||||
let config = if let Some(aux) = utils::get_config().await {
|
|
||||||
aux
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cloudflare =
|
let mut cloudflare =
|
||||||
@ -67,18 +32,16 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut conn, mut _handle, mut messages) = new_connection().unwrap();
|
let mut network_change_listener = match NetworkChangeListener::new() {
|
||||||
let groups = nl_mgrp(RTNLGRP_LINK) | nl_mgrp(RTNLGRP_IPV4_IFADDR);
|
Some(aux) => {
|
||||||
|
info!("Listening for IPv4 address changes and interface connect/disconnect events...");
|
||||||
let addr = SocketAddr::new(0, groups);
|
aux
|
||||||
|
}
|
||||||
if let Err(e) = conn.socket_mut().socket_mut().bind(&addr) {
|
None => {
|
||||||
error!("Failed to bind to socket: {:?}", &e);
|
error!("Failed to initialize networkchangelistener");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
tokio::spawn(conn);
|
|
||||||
info!("Listening for IPv4 address changes and interface connect/disconnect events...");
|
|
||||||
|
|
||||||
let mut message_handler = MessageHandler::new(
|
let mut message_handler = MessageHandler::new(
|
||||||
&mut cloudflare,
|
&mut cloudflare,
|
||||||
@ -91,22 +54,22 @@ async fn main() {
|
|||||||
interval
|
interval
|
||||||
});
|
});
|
||||||
|
|
||||||
while !should_exit.load(Ordering::SeqCst) {
|
while !exit_listener.should_exit() {
|
||||||
let tick_future = match interval.as_mut() {
|
let tick_future = match interval.as_mut() {
|
||||||
Some(interval) => Either::Left(interval.tick()),
|
Some(interval) => Either::Left(interval.tick()),
|
||||||
None => Either::Right(future::pending::<tokio::time::Instant>()),
|
None => Either::Right(future::pending::<tokio::time::Instant>()),
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = notify.notified() => break,
|
_ = exit_listener.notified() => break,
|
||||||
_ = tick_future => {
|
_ = tick_future => {
|
||||||
if let Some(duration) = config.max_duration.as_ref() {
|
if let Some(duration) = config.max_duration.as_ref() {
|
||||||
let duration_string = duration_to_string(duration);
|
let duration_string = utils::duration_to_string(duration);
|
||||||
let log_string = format!("{} has passed since last check, checking...", duration_string.trim());
|
let log_string = format!("{} has passed since last check, checking...", duration_string.trim());
|
||||||
message_handler.log_and_check(Some(&log_string), Option::<&&str>::None).await;
|
message_handler.log_and_check(Some(&log_string), Option::<&&str>::None).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message = messages.next() => {
|
message = network_change_listener.next_message() => {
|
||||||
if let Some((message, _)) = message {
|
if let Some((message, _)) = message {
|
||||||
if let Some(interval) = interval.as_mut() {
|
if let Some(interval) = interval.as_mut() {
|
||||||
interval.reset();
|
interval.reset();
|
||||||
|
60
src/network_change_listener.rs
Normal file
60
src/network_change_listener.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use futures::{
|
||||||
|
channel::mpsc::UnboundedReceiver,
|
||||||
|
stream::{Next, StreamExt},
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use netlink_packet_core::NetlinkMessage;
|
||||||
|
use netlink_packet_route::RouteNetlinkMessage;
|
||||||
|
use netlink_sys::{AsyncSocket, SocketAddr};
|
||||||
|
use rtnetlink::new_connection;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
const RTNLGRP_LINK: u32 = 1;
|
||||||
|
const RTNLGRP_IPV4_IFADDR: u32 = 5;
|
||||||
|
|
||||||
|
const fn nl_mgrp(group: u32) -> u32 {
|
||||||
|
if group > 31 {
|
||||||
|
panic!("use netlink_sys::Socket::add_membership() for this group");
|
||||||
|
}
|
||||||
|
if group == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1 << (group - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Messages = UnboundedReceiver<(NetlinkMessage<RouteNetlinkMessage>, SocketAddr)>;
|
||||||
|
pub struct NetworkChangeListener {
|
||||||
|
messages: Messages,
|
||||||
|
thread_handle: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkChangeListener {
|
||||||
|
pub fn new() -> Option<Self> {
|
||||||
|
let (mut conn, mut _handle, messages) = new_connection().ok()?;
|
||||||
|
let groups = nl_mgrp(RTNLGRP_LINK) | nl_mgrp(RTNLGRP_IPV4_IFADDR);
|
||||||
|
|
||||||
|
let addr = SocketAddr::new(0, groups);
|
||||||
|
|
||||||
|
if let Err(e) = conn.socket_mut().socket_mut().bind(&addr) {
|
||||||
|
error!("Failed to bind to socket: {:?}", &e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let thread_handle = tokio::spawn(conn);
|
||||||
|
Some(Self {
|
||||||
|
messages,
|
||||||
|
thread_handle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_message(&mut self) -> Next<'_, Messages> {
|
||||||
|
self.messages.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NetworkChangeListener {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.thread_handle.abort();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user