Skip to content

Commit

Permalink
Only route local dns to active sessions (#60)
Browse files Browse the repository at this point in the history
- 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
- An annoying side-effect of this is still that linkup needs to be
started (for dnsmasq to be started) to view sessions, but 🤷
  • Loading branch information
ostenbom authored Feb 21, 2024
1 parent b268716 commit 6d89ac2
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 48 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion linkup-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "linkup-cli"
version = "0.2.7"
version = "0.2.8"
edition = "2021"

[[bin]]
Expand Down
83 changes: 57 additions & 26 deletions linkup-cli/src/local_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,46 @@ 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 {
#[error("reqwest proxy error {0}")]
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<MemoryStringStore>,
req_body: web::Bytes,
Expand All @@ -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)),
Expand Down Expand Up @@ -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<String>, session_name: String) -> HttpResponse {
let dnsmasq_conf_domains = session_domains
.iter()
.map(|d| format!("{}.{}", session_name, d))
.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)
}
2 changes: 2 additions & 0 deletions linkup-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
72 changes: 52 additions & 20 deletions linkup-cli/src/services/dnsmasq.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::{
fs,
path::Path,
process::{Command, Stdio},
};

use nix::sys::signal::Signal;
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};

Expand All @@ -14,18 +15,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())
Expand All @@ -45,14 +35,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<Vec<String>>) -> Result<String> {
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().fold(String::new(), |mut acc, d| {
let _ = write!(acc, "address=/{}/127.0.0.1\naddress=/{}/::1\n", d, d);
acc
});
}

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(),
Expand All @@ -61,9 +67,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::<i32>()
.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(())
}

0 comments on commit 6d89ac2

Please sign in to comment.