From e1e4d66d492c62e08d118f78b629b25813951377 Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Wed, 21 Feb 2024 10:57:47 +0100 Subject: [PATCH 1/3] Only route local dns to active sessions - Users may want to view other linkup sessions, or preview environments. - With local-dns enabled, all these are routed to the local server where the session does not exist - Default dnsmasq to route to the public internet, but add special rules for the active local session instead --- linkup-cli/src/local_server.rs | 83 ++++++++++++++++++++---------- linkup-cli/src/main.rs | 2 + linkup-cli/src/services/dnsmasq.rs | 71 ++++++++++++++++++------- 3 files changed, 110 insertions(+), 46 deletions(-) diff --git a/linkup-cli/src/local_server.rs b/linkup-cli/src/local_server.rs index fa64488..4efa495 100644 --- a/linkup-cli/src/local_server.rs +++ b/linkup-cli/src/local_server.rs @@ -9,7 +9,11 @@ use thiserror::Error; use linkup::{HeaderMap as LinkupHeaderMap, HeaderName as LinkupHeaderName, *}; -use crate::LINKUP_LOCALSERVER_PORT; +use crate::{ + linkup_file_path, + services::dnsmasq::{reload_dnsmasq_conf, write_dnsmaq_conf}, + LINKUP_LOCALDNS_INSTALL, LINKUP_LOCALSERVER_PORT, +}; #[derive(Error, Debug)] pub enum ProxyError { @@ -17,6 +21,34 @@ pub enum ProxyError { ReqwestProxyError(String), } +#[actix_web::main] +pub async fn local_linkup_main() -> io::Result<()> { + env_logger::Builder::new() + .filter(None, log::LevelFilter::Info) + .init(); + + let string_store = web::Data::new(MemoryStringStore::new()); + + println!("Starting local server on port {}", LINKUP_LOCALSERVER_PORT); + HttpServer::new(move || { + App::new() + .app_data(string_store.clone()) // Add shared state + .app_data(web::PayloadConfig::new(100 * 1024 * 1024)) // 100 MB payload limit + .wrap(middleware::Logger::default()) // Enable logger + .route("/linkup", web::post().to(linkup_config_handler)) + .route("/linkup-check", web::route().to(always_ok)) + .service( + web::resource("{path:.*}") + .guard(guard::Header("upgrade", "websocket")) + .to(linkup_ws_request_handler), + ) + .default_service(web::route().to(linkup_request_handler)) + }) + .bind(("127.0.0.1", LINKUP_LOCALSERVER_PORT))? + .run() + .await +} + async fn linkup_config_handler( string_store: web::Data, req_body: web::Bytes, @@ -33,11 +65,18 @@ async fn linkup_config_handler( match update_session_req_from_json(input_json_conf) { Ok((desired_name, server_conf)) => { let sessions = SessionAllocator::new(string_store.as_ref()); + let server_domains = server_conf.domain_selection_order.clone(); let session_name = sessions .store_session(server_conf, NameKind::Animal, desired_name) .await; match session_name { - Ok(session_name) => HttpResponse::Ok().body(session_name), + Ok(session_name) => { + if linkup_file_path(LINKUP_LOCALDNS_INSTALL).exists() { + return add_local_dns_domain(server_domains, session_name); + } + + HttpResponse::Ok().body(session_name) + } Err(e) => HttpResponse::InternalServerError() .append_header(ContentType::plaintext()) .body(format!("Failed to store server config: {}", e)), @@ -258,30 +297,22 @@ fn no_redirect_client() -> reqwest::Client { .unwrap() } -#[actix_web::main] -pub async fn local_linkup_main() -> io::Result<()> { - env_logger::Builder::new() - .filter(None, log::LevelFilter::Info) - .init(); +fn add_local_dns_domain(session_domains: Vec, session_name: String) -> HttpResponse { + let dnsmasq_conf_domains = session_domains + .iter() + .map(|d| format!("{}.{}", session_name, d.to_string())) + .collect(); - let string_store = web::Data::new(MemoryStringStore::new()); + if let Err(e) = write_dnsmaq_conf(Some(dnsmasq_conf_domains)) { + return HttpResponse::InternalServerError() + .append_header(ContentType::plaintext()) + .body(format!("Failed to write dnsmasq config: {}", e)); + }; + if let Err(e) = reload_dnsmasq_conf() { + return HttpResponse::InternalServerError() + .append_header(ContentType::plaintext()) + .body(format!("Failed to restart dnsmasq: {}", e)); + }; - println!("Starting local server on port {}", LINKUP_LOCALSERVER_PORT); - HttpServer::new(move || { - App::new() - .app_data(string_store.clone()) // Add shared state - .app_data(web::PayloadConfig::new(100 * 1024 * 1024)) // 100 MB payload limit - .wrap(middleware::Logger::default()) // Enable logger - .route("/linkup", web::post().to(linkup_config_handler)) - .route("/linkup-check", web::route().to(always_ok)) - .service( - web::resource("{path:.*}") - .guard(guard::Header("upgrade", "websocket")) - .to(linkup_ws_request_handler), - ) - .default_service(web::route().to(linkup_request_handler)) - }) - .bind(("127.0.0.1", LINKUP_LOCALSERVER_PORT))? - .run() - .await + HttpResponse::Ok().body(session_name) } diff --git a/linkup-cli/src/main.rs b/linkup-cli/src/main.rs index a9a5853..f314593 100644 --- a/linkup-cli/src/main.rs +++ b/linkup-cli/src/main.rs @@ -117,6 +117,8 @@ pub enum CliError { LocalDNSUninstall(String), #[error("failed to write file: {0}")] WriteFile(String), + #[error("failed to reboot dnsmasq: {0}")] + RebootDNSMasq(String), } #[derive(Parser)] diff --git a/linkup-cli/src/services/dnsmasq.rs b/linkup-cli/src/services/dnsmasq.rs index 4e8cf4e..7c28032 100644 --- a/linkup-cli/src/services/dnsmasq.rs +++ b/linkup-cli/src/services/dnsmasq.rs @@ -1,10 +1,10 @@ use std::{ fs, - path::Path, process::{Command, Stdio}, }; -use nix::sys::signal::Signal; +use nix::sys::signal::{kill, Signal}; +use nix::unistd::Pid; use crate::{linkup_dir_path, linkup_file_path, stop::stop_pid_file, CliError, Result}; @@ -14,18 +14,7 @@ const LOG_FILE: &str = "dnsmasq-log"; const PID_FILE: &str = "dnsmasq-pid"; pub fn start() -> Result<()> { - let conf_file_path = linkup_file_path(CONF_FILE); - let logfile_path = linkup_file_path(LOG_FILE); - let pidfile_path = linkup_file_path(PID_FILE); - - if fs::write(&logfile_path, "").is_err() { - return Err(CliError::WriteFile(format!( - "Failed to write dnsmasq log file at {}", - logfile_path.display() - ))); - } - - write_conf_file(&conf_file_path, &logfile_path, &pidfile_path)?; + let conf_file_path = write_dnsmaq_conf(None)?; Command::new("dnsmasq") .current_dir(linkup_dir_path()) @@ -45,14 +34,30 @@ pub fn stop() { let _ = stop_pid_file(&linkup_file_path(PID_FILE), Signal::SIGTERM); } -fn write_conf_file(conf_file_path: &Path, logfile_path: &Path, pidfile_path: &Path) -> Result<()> { +pub fn write_dnsmaq_conf(local_domains: Option>) -> Result { + let conf_file_path = linkup_file_path(CONF_FILE); + let logfile_path = linkup_file_path(LOG_FILE); + let pidfile_path = linkup_file_path(PID_FILE); + let mut local_domains_template = String::new(); + if let Some(local_domains) = local_domains { + local_domains_template = local_domains + .iter() + .map(|d| format!("address=/{}/127.0.0.1\naddress=/{}/::1\n", d, d)) + .collect() + } + let dnsmasq_template = format!( " - address=/#/127.0.0.1 - port={} - log-facility={} - pid-file={} +# Set of domains that should be routed locally +{} + +# Other dnsmasq config options +server=1.1.1.1 +port={} +log-facility={} +pid-file={} ", + local_domains_template, PORT, logfile_path.display(), pidfile_path.display(), @@ -61,9 +66,35 @@ fn write_conf_file(conf_file_path: &Path, logfile_path: &Path, pidfile_path: &Pa if fs::write(conf_file_path, dnsmasq_template).is_err() { return Err(CliError::WriteFile(format!( "Failed to write dnsmasq config at {}", - conf_file_path.display() + linkup_file_path(CONF_FILE).display() ))); } + Ok(linkup_file_path(CONF_FILE) + .to_str() + .expect("const path known to be valid") + .to_string()) +} + +pub fn reload_dnsmasq_conf() -> Result<()> { + let pidfile_path = linkup_file_path(PID_FILE); + let pid_str = fs::read_to_string(pidfile_path).map_err(|e| { + CliError::RebootDNSMasq(format!( + "Failed to read PID file at {}: {}", + linkup_file_path(PID_FILE).display(), + e + )) + })?; + + // Parse the PID from the file content + let pid = pid_str + .trim() + .parse::() + .map_err(|e| CliError::RebootDNSMasq(format!("Invalid PID value: {}", e)))?; + + // Send SIGHUP signal to the dnsmasq process + kill(Pid::from_raw(pid), Signal::SIGHUP) + .map_err(|e| CliError::RebootDNSMasq(format!("Failed to send SIGHUP to dnsmasq: {}", e)))?; + Ok(()) } From 53b1db6fd2b477767ac07f14297d400d68e3e73f Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Wed, 21 Feb 2024 11:00:01 +0100 Subject: [PATCH 2/3] Bump cli version --- Cargo.lock | 2 +- linkup-cli/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c6245b..f858028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1042,7 +1042,7 @@ dependencies = [ [[package]] name = "linkup-cli" -version = "0.2.7" +version = "0.2.8" dependencies = [ "actix-web", "clap", diff --git a/linkup-cli/Cargo.toml b/linkup-cli/Cargo.toml index 2cada95..a882392 100644 --- a/linkup-cli/Cargo.toml +++ b/linkup-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linkup-cli" -version = "0.2.7" +version = "0.2.8" edition = "2021" [[bin]] From 56d26c3b4825b9812cb8078257554b24a666167b Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Wed, 21 Feb 2024 11:07:30 +0100 Subject: [PATCH 3/3] Fix clippy warnings --- linkup-cli/src/local_server.rs | 2 +- linkup-cli/src/services/dnsmasq.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/linkup-cli/src/local_server.rs b/linkup-cli/src/local_server.rs index 4efa495..f6670dd 100644 --- a/linkup-cli/src/local_server.rs +++ b/linkup-cli/src/local_server.rs @@ -300,7 +300,7 @@ fn no_redirect_client() -> reqwest::Client { fn add_local_dns_domain(session_domains: Vec, session_name: String) -> HttpResponse { let dnsmasq_conf_domains = session_domains .iter() - .map(|d| format!("{}.{}", session_name, d.to_string())) + .map(|d| format!("{}.{}", session_name, d)) .collect(); if let Err(e) = write_dnsmaq_conf(Some(dnsmasq_conf_domains)) { diff --git a/linkup-cli/src/services/dnsmasq.rs b/linkup-cli/src/services/dnsmasq.rs index 7c28032..1029d08 100644 --- a/linkup-cli/src/services/dnsmasq.rs +++ b/linkup-cli/src/services/dnsmasq.rs @@ -5,6 +5,7 @@ use std::{ use nix::sys::signal::{kill, Signal}; use nix::unistd::Pid; +use std::fmt::Write; use crate::{linkup_dir_path, linkup_file_path, stop::stop_pid_file, CliError, Result}; @@ -40,10 +41,10 @@ pub fn write_dnsmaq_conf(local_domains: Option>) -> Result { let pidfile_path = linkup_file_path(PID_FILE); let mut local_domains_template = String::new(); if let Some(local_domains) = local_domains { - local_domains_template = local_domains - .iter() - .map(|d| format!("address=/{}/127.0.0.1\naddress=/{}/::1\n", d, d)) - .collect() + local_domains_template = local_domains.iter().fold(String::new(), |mut acc, d| { + let _ = write!(acc, "address=/{}/127.0.0.1\naddress=/{}/::1\n", d, d); + acc + }); } let dnsmasq_template = format!(