From 22af45fb6e5fde98ef0d96a3d77fd2912560f7a0 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Mon, 27 Jun 2022 10:53:06 -0600 Subject: [PATCH] add dns server to embassy-os (#1572) * add dns server to embassy-os * fix initialization * multiple ip addresses --- backend/Cargo.lock | 131 +++++++++++++++++ backend/Cargo.toml | 3 +- backend/src/backup/restore.rs | 2 +- backend/src/context/rpc.rs | 5 + backend/src/disk/mount/filesystem/cifs.rs | 2 +- backend/src/disk/mount/filesystem/mod.rs | 2 +- backend/src/manager/mod.rs | 1 + backend/src/net/dns.rs | 171 ++++++++++++++++++++++ backend/src/net/mdns.rs | 4 +- backend/src/net/mod.rs | 16 +- build/initialization.sh | 5 + 11 files changed, 332 insertions(+), 10 deletions(-) create mode 100644 backend/src/net/dns.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9368da7e3..2f7f496db 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -814,6 +814,12 @@ dependencies = [ "parking_lot_core 0.9.3", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "data-url" version = "0.1.1" @@ -1121,6 +1127,7 @@ dependencies = [ "tracing-error", "tracing-futures", "tracing-subscriber", + "trust-dns-server", "typed-builder", "url", ] @@ -1161,6 +1168,24 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck 0.4.0", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.96", +] + [[package]] name = "enum_kind" version = "0.2.1" @@ -2237,6 +2262,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.19.1" @@ -2412,6 +2446,15 @@ dependencies = [ "syn 1.0.96", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.28.4" @@ -2922,6 +2965,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.7.3" @@ -4445,6 +4498,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", +] + [[package]] name = "time-macros" version = "0.1.1" @@ -4788,6 +4851,74 @@ dependencies = [ "serde_json", ] +[[package]] +name = "trust-dns-client" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d9ba1c6079f6f9b4664e482db1700bd53d2ee77b1c9752c1d7a66c0c8bda99" +dependencies = [ + "cfg-if 1.0.0", + "data-encoding", + "futures-channel", + "futures-util", + "lazy_static", + "log", + "radix_trie", + "rand 0.8.5", + "thiserror", + "time 0.3.11", + "tokio 1.15.0", + "trust-dns-proto", +] + +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "lazy_static", + "log", + "rand 0.8.5", + "smallvec", + "thiserror", + "tinyvec", + "tokio 1.15.0", + "url", +] + +[[package]] +name = "trust-dns-server" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a395a2e0fd8aac9b4613767a5b4ba4b2040de1b767fa03ace8c9d6f351d60b2d" +dependencies = [ + "async-trait", + "bytes 1.1.0", + "cfg-if 1.0.0", + "enum-as-inner", + "env_logger 0.9.0", + "futures-executor", + "futures-util", + "log", + "serde", + "thiserror", + "time 0.3.11", + "tokio 1.15.0", + "toml", + "trust-dns-client", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 51c1e1f07..bbc29e7f5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -47,8 +47,8 @@ unstable = ["patch-db/unstable"] [dependencies] aes = { version = "0.7.5", features = ["ctr"] } -async-trait = "0.1.51" async-stream = "0.3.3" +async-trait = "0.1.51" avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", version = "0.10.0", branch = "feature/dynamic-linking", features = [ "dynamic", ], optional = true } @@ -138,6 +138,7 @@ tracing = "0.1" tracing-error = "0.1" tracing-futures = "0.2" tracing-subscriber = "0.2" +trust-dns-server = "0.21.2" typed-builder = "0.9.1" url = { version = "2.2.2", features = ["serde"] } diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index 9b27fc10a..4a3994c85 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -27,6 +27,7 @@ use crate::disk::mount::guard::TmpMountGuard; use crate::install::progress::InstallProgress; use crate::install::{download_install_s9pk, PKG_PUBLIC_DIR}; use crate::net::ssl::SslManager; +use crate::notifications::NotificationLevel; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::reader::S9pkReader; use crate::setup::RecoveryStatus; @@ -34,7 +35,6 @@ use crate::util::display_none; use crate::util::io::dir_size; use crate::util::serde::IoFormat; use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR}; -use crate::{auth::check_password_against_db, notifications::NotificationLevel}; use crate::{Error, ResultExt}; fn parse_comma_separated(arg: &str, _: &ArgMatches<'_>) -> Result, Error> { diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index e576fe864..a4bd7a77f 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -45,6 +45,7 @@ pub struct RpcContextConfig { pub bind_static: Option, pub tor_control: Option, pub tor_socks: Option, + pub dns_bind: Option>, pub revision_cache_size: Option, pub datadir: Option, pub log_server: Option, @@ -222,6 +223,10 @@ impl RpcContext { crate::net::tor::os_key(&mut secret_store.acquire().await?).await?, base.tor_control .unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))), + base.dns_bind + .as_ref() + .map(|v| v.as_slice()) + .unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]), secret_store.clone(), None, ) diff --git a/backend/src/disk/mount/filesystem/cifs.rs b/backend/src/disk/mount/filesystem/cifs.rs index b7e9ddf02..5eafe2841 100644 --- a/backend/src/disk/mount/filesystem/cifs.rs +++ b/backend/src/disk/mount/filesystem/cifs.rs @@ -18,7 +18,7 @@ use crate::Error; async fn resolve_hostname(hostname: &str) -> Result { #[cfg(feature = "avahi")] if hostname.ends_with(".local") { - return Ok(crate::net::mdns::resolve_mdns(hostname).await?); + return Ok(IpAddr::V4(crate::net::mdns::resolve_mdns(hostname).await?)); } Ok(String::from_utf8( Command::new("nmblookup") diff --git a/backend/src/disk/mount/filesystem/mod.rs b/backend/src/disk/mount/filesystem/mod.rs index f24383f3f..01fe48d8c 100644 --- a/backend/src/disk/mount/filesystem/mod.rs +++ b/backend/src/disk/mount/filesystem/mod.rs @@ -2,7 +2,7 @@ use std::path::Path; use async_trait::async_trait; use digest::generic_array::GenericArray; -use digest::{Digest, OutputSizeUser}; +use digest::OutputSizeUser; use sha2::Sha256; use crate::Error; diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 29ec33587..efe654481 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -293,6 +293,7 @@ async fn run_main( .net_controller .remove( &state.manifest.id, + ip, state.manifest.interfaces.0.keys().cloned(), ) .await?; diff --git a/backend/src/net/dns.rs b/backend/src/net/dns.rs new file mode 100644 index 000000000..360d2bdd8 --- /dev/null +++ b/backend/src/net/dns.rs @@ -0,0 +1,171 @@ +use std::borrow::Borrow; +use std::collections::{BTreeMap, BTreeSet}; +use std::net::{Ipv4Addr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; + +use futures::TryFutureExt; +use helpers::NonDetachingJoinHandle; +use models::PackageId; +use tokio::net::{TcpListener, UdpSocket}; +use tokio::sync::RwLock; +use trust_dns_server::authority::MessageResponseBuilder; +use trust_dns_server::client::op::{Header, ResponseCode}; +use trust_dns_server::client::rr::{Name, Record, RecordType}; +use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; +use trust_dns_server::ServerFuture; + +use crate::net::mdns::resolve_mdns; +use crate::{Error, ErrorKind, ResultExt}; + +pub struct DnsController { + services: Arc>>>, + #[allow(dead_code)] + dns_server: NonDetachingJoinHandle>, +} + +struct Resolver { + services: Arc>>>, +} +impl Resolver { + async fn resolve(&self, name: &Name) -> Option> { + match name.iter().next_back() { + Some(b"local") => match resolve_mdns(&format!( + "{}.local", + name.iter() + .rev() + .skip(1) + .next() + .and_then(|v| std::str::from_utf8(v).ok()) + .unwrap_or_default() + )) + .await + { + Ok(ip) => Some(vec![ip]), + Err(e) => { + tracing::error!("{}", e); + tracing::debug!("{:?}", e); + None + } + }, + Some(b"embassy") => { + if let Some(pkg) = name.iter().rev().skip(1).next() { + if let Some(ip) = self + .services + .read() + .await + .get(std::str::from_utf8(pkg).unwrap_or_default()) + { + Some(ip.iter().copied().collect()) + } else { + None + } + } else { + None + } + } + _ => None, + } + } +} + +#[async_trait::async_trait] +impl RequestHandler for Resolver { + async fn handle_request( + &self, + request: &Request, + mut response_handle: R, + ) -> ResponseInfo { + let query = request.request_info().query; + if let Some(ip) = self.resolve(query.name().borrow()).await { + if query.query_type() != RecordType::A { + tracing::warn!("Non A-Record requested for {}", query.name()); + } + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + Header::response_from_request(request.header()), + &ip.into_iter() + .map(|ip| { + Record::from_rdata( + request.request_info().query.name().to_owned().into(), + 0, + trust_dns_server::client::rr::RData::A(ip), + ) + }) + .collect::>(), + [], + [], + [], + ), + ) + .await + } else { + let mut res = Header::response_from_request(request.header()); + res.set_response_code(ResponseCode::NXDomain); + response_handle + .send_response( + MessageResponseBuilder::from_message_request(&*request).build( + res.into(), + [], + [], + [], + [], + ), + ) + .await + } + .unwrap_or_else(|e| { + tracing::error!("{}", e); + tracing::debug!("{:?}", e); + let mut res = Header::response_from_request(request.header()); + res.set_response_code(ResponseCode::ServFail); + res.into() + }) + } +} + +impl DnsController { + pub async fn init(bind: &[SocketAddr]) -> Result { + let services = Arc::new(RwLock::new(BTreeMap::new())); + + let mut server = ServerFuture::new(Resolver { + services: services.clone(), + }); + server.register_listener( + TcpListener::bind(bind) + .await + .with_kind(ErrorKind::Network)?, + Duration::from_secs(30), + ); + server.register_socket(UdpSocket::bind(bind).await.with_kind(ErrorKind::Network)?); + + let dns_server = tokio::spawn( + server + .block_until_done() + .map_err(|e| Error::new(e, ErrorKind::Network)), + ) + .into(); + + Ok(Self { + services, + dns_server, + }) + } + + pub async fn add(&self, pkg_id: &PackageId, ip: Ipv4Addr) { + let mut writable = self.services.write().await; + let mut ips = writable.remove(pkg_id).unwrap_or_default(); + ips.insert(ip); + writable.insert(pkg_id.clone(), ips); + } + + pub async fn remove(&self, pkg_id: &PackageId, ip: Ipv4Addr) { + let mut writable = self.services.write().await; + let mut ips = writable.remove(pkg_id).unwrap_or_default(); + ips.remove(&ip); + if !ips.is_empty() { + writable.insert(pkg_id.clone(), ips); + } + } +} diff --git a/backend/src/net/mdns.rs b/backend/src/net/mdns.rs index 775e4eb73..2a5f680e3 100644 --- a/backend/src/net/mdns.rs +++ b/backend/src/net/mdns.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::net::IpAddr; +use std::net::Ipv4Addr; use avahi_sys::{ self, avahi_client_errno, avahi_entry_group_add_service, avahi_entry_group_commit, @@ -17,7 +17,7 @@ use crate::s9pk::manifest::PackageId; use crate::util::Invoke; use crate::Error; -pub async fn resolve_mdns(hostname: &str) -> Result { +pub async fn resolve_mdns(hostname: &str) -> Result { Ok(String::from_utf8( Command::new("avahi-resolve-host-name") .arg("-4") diff --git a/backend/src/net/mod.rs b/backend/src/net/mod.rs index f087f29aa..793111173 100644 --- a/backend/src/net/mod.rs +++ b/backend/src/net/mod.rs @@ -14,11 +14,13 @@ use self::mdns::MdnsController; use self::nginx::NginxController; use self::ssl::SslManager; use self::tor::TorController; +use crate::net::dns::DnsController; use crate::net::interface::TorConfig; use crate::net::nginx::InterfaceMetadata; use crate::s9pk::manifest::PackageId; use crate::Error; +pub mod dns; pub mod interface; #[cfg(feature = "avahi")] pub mod mdns; @@ -45,6 +47,7 @@ pub struct NetController { pub mdns: MdnsController, pub nginx: NginxController, pub ssl: SslManager, + pub dns: DnsController, } impl NetController { #[instrument(skip(db))] @@ -52,6 +55,7 @@ impl NetController { embassyd_addr: SocketAddr, embassyd_tor_key: TorSecretKeyV3, tor_control: SocketAddr, + dns_bind: &[SocketAddr], db: SqlitePool, import_root_ca: Option<(PKey, X509)>, ) -> Result { @@ -65,6 +69,7 @@ impl NetController { mdns: MdnsController::init(), nginx: NginxController::init(PathBuf::from("/etc/nginx"), &ssl).await?, ssl, + dns: DnsController::init(dns_bind).await?, }) } @@ -92,7 +97,7 @@ impl NetController { Some(cfg) => Some((i.0, cfg, i.2)), }) .collect::>(); - let (tor_res, _, nginx_res) = tokio::join!( + let (tor_res, _, nginx_res, _) = tokio::join!( self.tor.add(pkg_id, ip, interfaces_tor), { #[cfg(feature = "avahi")] @@ -123,7 +128,8 @@ impl NetController { )), }); self.nginx.add(&self.ssl, pkg_id.clone(), ip, interfaces) - } + }, + self.dns.add(pkg_id, ip), ); tor_res?; nginx_res?; @@ -135,9 +141,10 @@ impl NetController { pub async fn remove + Clone>( &self, pkg_id: &PackageId, + ip: Ipv4Addr, interfaces: I, ) -> Result<(), Error> { - let (tor_res, _, nginx_res) = tokio::join!( + let (tor_res, _, nginx_res, _) = tokio::join!( self.tor.remove(pkg_id, interfaces.clone()), { #[cfg(feature = "avahi")] @@ -146,7 +153,8 @@ impl NetController { let mdns_fut = futures::future::ready(()); mdns_fut }, - self.nginx.remove(pkg_id) + self.nginx.remove(pkg_id), + self.dns.remove(pkg_id, ip), ); tor_res?; nginx_res?; diff --git a/build/initialization.sh b/build/initialization.sh index 85a0efe2f..71ffaf251 100755 --- a/build/initialization.sh +++ b/build/initialization.sh @@ -80,6 +80,11 @@ ControlPort 9051 CookieAuthentication 1 EOF +# enable embassyd dns server +systemctl enable systemd-resolved +sed -i '/\(^\|#\)DNS=/c\DNS=127.0.0.1' /etc/systemd/resolved.conf +sed -i '/prepend domain-name-servers/c\prepend domain-name-servers 127.0.0.53;' /etc/dhcp/dhclient.conf + if [ -f /embassy-os/product_key.txt ] then cat /embassy-os/product_key.txt | tr -d '\n' | sha256sum | head -c 32 | sed 's/$/\n/' > /etc/machine-id