diff --git a/Makefile b/Makefile index 52018f3ac..2e3cfe52a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ENVIRONMENT_FILE := $(shell ./check-environment.sh) GIT_HASH_FILE := $(shell ./check-git-hash.sh) -EMBASSY_BINS := backend/target/aarch64-unknown-linux-gnu/release/embassyd backend/target/aarch64-unknown-linux-gnu/release/embassy-init backend/target/aarch64-unknown-linux-gnu/release/embassy-cli backend/target/aarch64-unknown-linux-gnu/release/embassy-sdk +EMBASSY_BINS := backend/target/aarch64-unknown-linux-gnu/release/embassyd backend/target/aarch64-unknown-linux-gnu/release/embassy-init backend/target/aarch64-unknown-linux-gnu/release/embassy-cli backend/target/aarch64-unknown-linux-gnu/release/embassy-sdk backend/target/aarch64-unknown-linux-gnu/release/avahi-alias EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnostic-ui EMBASSY_SRC := raspios.img product_key.txt $(EMBASSY_BINS) backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(shell find build) COMPAT_SRC := $(shell find system-images/compat/src) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 62c106ab0..d0870ce52 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -36,6 +36,10 @@ path = "src/bin/embassy-sdk.rs" name = "embassy-cli" path = "src/bin/embassy-cli.rs" +[[bin]] +name = "avahi-alias" +path = "src/bin/avahi-alias.rs" + [features] avahi = ["avahi-sys"] default = ["avahi", "sound", "metal", "js_engine"] diff --git a/backend/src/bin/avahi-alias.rs b/backend/src/bin/avahi-alias.rs new file mode 100644 index 000000000..22349a927 --- /dev/null +++ b/backend/src/bin/avahi-alias.rs @@ -0,0 +1,163 @@ +use avahi_sys::{ + self, avahi_client_errno, avahi_entry_group_add_service, avahi_entry_group_commit, + avahi_strerror, AvahiClient, +}; + +fn log_str_error(action: &str, e: i32) { + unsafe { + let e_str = avahi_strerror(e); + eprintln!( + "Could not {}: {:?}", + action, + std::ffi::CStr::from_ptr(e_str) + ); + } +} + +fn main() { + let aliases: Vec<_> = std::env::args().skip(1).collect(); + unsafe { + let simple_poll = avahi_sys::avahi_simple_poll_new(); + let poll = avahi_sys::avahi_simple_poll_get(simple_poll); + let mut box_err = Box::pin(0 as i32); + let err_c: *mut i32 = box_err.as_mut().get_mut(); + let avahi_client = avahi_sys::avahi_client_new( + poll, + avahi_sys::AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, + Some(client_callback), + std::ptr::null_mut(), + err_c, + ); + if avahi_client == std::ptr::null_mut::() { + log_str_error("create Avahi client", *box_err); + panic!("Failed to create Avahi Client"); + } + let group = avahi_sys::avahi_entry_group_new( + avahi_client, + Some(entry_group_callback), + std::ptr::null_mut(), + ); + if group == std::ptr::null_mut() { + log_str_error("create Avahi entry group", avahi_client_errno(avahi_client)); + panic!("Failed to create Avahi Entry Group"); + } + let mut hostname_buf = vec![0]; + let hostname_raw = avahi_sys::avahi_client_get_host_name_fqdn(avahi_client); + hostname_buf.extend_from_slice(std::ffi::CStr::from_ptr(hostname_raw).to_bytes_with_nul()); + let buflen = hostname_buf.len(); + debug_assert!(hostname_buf.ends_with(b".local\0")); + debug_assert!(!hostname_buf[..(buflen - 7)].contains(&b'.')); + // assume fixed length prefix on hostname due to local address + hostname_buf[0] = (buflen - 8) as u8; // set the prefix length to len - 8 (leading byte, .local, nul) for the main address + hostname_buf[buflen - 7] = 5; // set the prefix length to 5 for "local" + let mut res; + let http_tcp_cstr = + std::ffi::CString::new("_http._tcp").expect("Could not cast _http._tcp to c string"); + res = avahi_entry_group_add_service( + group, + avahi_sys::AVAHI_IF_UNSPEC, + avahi_sys::AVAHI_PROTO_UNSPEC, + avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST, + hostname_raw, + http_tcp_cstr.as_ptr(), + std::ptr::null(), + std::ptr::null(), + 443, + // below is a secret final argument that the type signature of this function does not tell you that it + // needs. This is because the C lib function takes a variable number of final arguments indicating the + // desired TXT records to add to this service entry. The way it decides when to stop taking arguments + // from the stack and dereferencing them is when it finds a null pointer...because fuck you, that's why. + // The consequence of this is that forgetting this last argument will cause segfaults or other undefined + // behavior. Welcome back to the stone age motherfucker. + std::ptr::null::(), + ); + if res < avahi_sys::AVAHI_OK { + log_str_error("add service to Avahi entry group", res); + panic!("Failed to load Avahi services"); + } + eprintln!("Published {:?}", std::ffi::CStr::from_ptr(hostname_raw)); + for alias in aliases { + let lan_address = alias + ".local"; + let lan_address_ptr = std::ffi::CString::new(lan_address) + .expect("Could not cast lan address to c string"); + res = avahi_sys::avahi_entry_group_add_record( + group, + avahi_sys::AVAHI_IF_UNSPEC, + avahi_sys::AVAHI_PROTO_UNSPEC, + avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST + | avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_ALLOW_MULTIPLE, + lan_address_ptr.as_ptr(), + avahi_sys::AVAHI_DNS_CLASS_IN as u16, + avahi_sys::AVAHI_DNS_TYPE_CNAME as u16, + avahi_sys::AVAHI_DEFAULT_TTL, + hostname_buf.as_ptr().cast(), + hostname_buf.len(), + ); + if res < avahi_sys::AVAHI_OK { + log_str_error("add CNAME record to Avahi entry group", res); + panic!("Failed to load Avahi services"); + } + eprintln!("Published {:?}", lan_address_ptr); + } + let commit_err = avahi_entry_group_commit(group); + if commit_err < avahi_sys::AVAHI_OK { + log_str_error("reset Avahi entry group", commit_err); + panic!("Failed to load Avahi services: reset"); + } + } + std::thread::park() +} + +unsafe extern "C" fn entry_group_callback( + _group: *mut avahi_sys::AvahiEntryGroup, + state: avahi_sys::AvahiEntryGroupState, + _userdata: *mut core::ffi::c_void, +) { + match state { + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_FAILURE => { + eprintln!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_FAILURE"); + } + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_COLLISION => { + eprintln!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_COLLISION"); + } + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_UNCOMMITED => { + eprintln!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_UNCOMMITED"); + } + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_ESTABLISHED => { + eprintln!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_ESTABLISHED"); + } + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_REGISTERING => { + eprintln!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_REGISTERING"); + } + other => { + eprintln!("AvahiCallback: EntryGroupState = {}", other); + } + } +} + +unsafe extern "C" fn client_callback( + _group: *mut avahi_sys::AvahiClient, + state: avahi_sys::AvahiClientState, + _userdata: *mut core::ffi::c_void, +) { + match state { + avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE => { + eprintln!("AvahiCallback: ClientState = AVAHI_CLIENT_FAILURE"); + } + avahi_sys::AvahiClientState_AVAHI_CLIENT_S_RUNNING => { + eprintln!("AvahiCallback: ClientState = AVAHI_CLIENT_S_RUNNING"); + } + avahi_sys::AvahiClientState_AVAHI_CLIENT_CONNECTING => { + eprintln!("AvahiCallback: ClientState = AVAHI_CLIENT_CONNECTING"); + } + avahi_sys::AvahiClientState_AVAHI_CLIENT_S_COLLISION => { + eprintln!("AvahiCallback: ClientState = AVAHI_CLIENT_S_COLLISION"); + } + avahi_sys::AvahiClientState_AVAHI_CLIENT_S_REGISTERING => { + eprintln!("AvahiCallback: ClientState = AVAHI_CLIENT_S_REGISTERING"); + } + other => { + eprintln!("AvahiCallback: ClientState = {}", other); + } + } +} diff --git a/backend/src/net/mdns.rs b/backend/src/net/mdns.rs index 33b67eede..74c7f5552 100644 --- a/backend/src/net/mdns.rs +++ b/backend/src/net/mdns.rs @@ -1,25 +1,20 @@ use std::collections::BTreeMap; use std::net::Ipv4Addr; -use avahi_sys::{ - self, avahi_client_errno, avahi_entry_group_add_service, avahi_entry_group_commit, - avahi_entry_group_free, avahi_free, avahi_strerror, AvahiClient, AvahiEntryGroup, -}; use color_eyre::eyre::eyre; -use libc::c_void; -use tokio::process::Command; +use tokio::process::{Child, Command}; use tokio::sync::Mutex; use torut::onion::TorSecretKeyV3; -use tracing::instrument; use super::interface::InterfaceId; use crate::s9pk::manifest::PackageId; use crate::util::Invoke; -use crate::Error; +use crate::{Error, ResultExt}; pub async fn resolve_mdns(hostname: &str) -> Result { Ok(String::from_utf8( Command::new("avahi-resolve-host-name") + .kill_on_drop(true) .arg("-4") .arg(hostname) .invoke(crate::ErrorKind::Network) @@ -39,258 +34,79 @@ pub async fn resolve_mdns(hostname: &str) -> Result { pub struct MdnsController(Mutex); impl MdnsController { - pub fn init() -> Self { - MdnsController(Mutex::new(MdnsControllerInner::init())) + pub async fn init() -> Result { + Ok(MdnsController(Mutex::new( + MdnsControllerInner::init().await?, + ))) } pub async fn add<'a, I: IntoIterator>( &self, pkg_id: &PackageId, interfaces: I, - ) { - self.0.lock().await.add(pkg_id, interfaces) + ) -> Result<(), Error> { + self.0.lock().await.add(pkg_id, interfaces).await } pub async fn remove>( &self, pkg_id: &PackageId, interfaces: I, - ) { - self.0.lock().await.remove(pkg_id, interfaces) + ) -> Result<(), Error> { + self.0.lock().await.remove(pkg_id, interfaces).await } } pub struct MdnsControllerInner { - entry_group: Option, + alias_cmd: Option, services: BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>, } -unsafe impl Send for MdnsControllerInner {} -unsafe impl Sync for MdnsControllerInner {} impl MdnsControllerInner { - fn init() -> Self { - MdnsControllerInner { - entry_group: Some(MdnsEntryGroup::init(&BTreeMap::new())), + async fn init() -> Result { + let mut res = MdnsControllerInner { + alias_cmd: None, services: BTreeMap::new(), + }; + res.sync().await?; + Ok(res) + } + async fn sync(&mut self) -> Result<(), Error> { + if let Some(mut cmd) = self.alias_cmd.take() { + cmd.kill().await.with_kind(crate::ErrorKind::Network)?; } + self.alias_cmd = Some( + Command::new("avahi-alias") + .kill_on_drop(true) + .args(self.services.iter().map(|(_, key)| { + key.public() + .get_onion_address() + .get_address_without_dot_onion() + })) + .spawn()?, + ); + Ok(()) } - fn sync(&mut self) { - drop(self.entry_group.take()); - self.entry_group = Some(MdnsEntryGroup::init(&self.services)); - } - fn add<'a, I: IntoIterator>( + async fn add<'a, I: IntoIterator>( &mut self, pkg_id: &PackageId, interfaces: I, - ) { + ) -> Result<(), Error> { self.services.extend( interfaces .into_iter() .map(|(interface_id, key)| ((pkg_id.clone(), interface_id), key)), ); - self.sync(); + self.sync().await?; + Ok(()) } - fn remove>(&mut self, pkg_id: &PackageId, interfaces: I) { + async fn remove>( + &mut self, + pkg_id: &PackageId, + interfaces: I, + ) -> Result<(), Error> { for interface_id in interfaces { self.services.remove(&(pkg_id.clone(), interface_id)); } - self.sync(); - } -} - -fn log_str_error(action: &str, e: i32) { - unsafe { - let e_str = avahi_strerror(e); - tracing::error!( - "Could not {}: {:?}", - action, - std::ffi::CStr::from_ptr(e_str) - ); - } -} - -struct MdnsEntryGroup { - hostname: Vec, - hostname_raw: *const libc::c_char, - entry_group: *mut AvahiEntryGroup, - _client_error: std::pin::Pin>, -} -impl MdnsEntryGroup { - #[instrument(skip(self))] - fn load_services(&mut self, services: &BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>) { - unsafe { - tracing::debug!("Loading services for mDNS"); - let mut res; - let http_tcp_cstr = std::ffi::CString::new("_http._tcp") - .expect("Could not cast _http._tcp to c string"); - res = avahi_entry_group_add_service( - self.entry_group, - avahi_sys::AVAHI_IF_UNSPEC, - avahi_sys::AVAHI_PROTO_UNSPEC, - avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST, - self.hostname_raw, - http_tcp_cstr.as_ptr(), - std::ptr::null(), - std::ptr::null(), - 443, - // below is a secret final argument that the type signature of this function does not tell you that it - // needs. This is because the C lib function takes a variable number of final arguments indicating the - // desired TXT records to add to this service entry. The way it decides when to stop taking arguments - // from the stack and dereferencing them is when it finds a null pointer...because fuck you, that's why. - // The consequence of this is that forgetting this last argument will cause segfaults or other undefined - // behavior. Welcome back to the stone age motherfucker. - std::ptr::null::(), - ); - if res < avahi_sys::AVAHI_OK { - log_str_error("add service to Avahi entry group", res); - panic!("Failed to load Avahi services"); - } - tracing::info!( - "Published {:?}", - std::ffi::CStr::from_ptr(self.hostname_raw) - ); - for key in services.values() { - let lan_address = key - .public() - .get_onion_address() - .get_address_without_dot_onion() - + ".local"; - tracing::debug!("Adding mdns CNAME entry for {}", &lan_address); - let lan_address_ptr = std::ffi::CString::new(lan_address) - .expect("Could not cast lan address to c string"); - res = avahi_sys::avahi_entry_group_add_record( - self.entry_group, - avahi_sys::AVAHI_IF_UNSPEC, - avahi_sys::AVAHI_PROTO_UNSPEC, - avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST - | avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_ALLOW_MULTIPLE, - lan_address_ptr.as_ptr(), - avahi_sys::AVAHI_DNS_CLASS_IN as u16, - avahi_sys::AVAHI_DNS_TYPE_CNAME as u16, - avahi_sys::AVAHI_DEFAULT_TTL, - self.hostname.as_ptr().cast(), - self.hostname.len(), - ); - if res < avahi_sys::AVAHI_OK { - log_str_error("add CNAME record to Avahi entry group", res); - panic!("Failed to load Avahi services"); - } - tracing::info!("Published {:?}", lan_address_ptr); - } - } - } - fn init(services: &BTreeMap<(PackageId, InterfaceId), TorSecretKeyV3>) -> Self { - unsafe { - tracing::debug!("Initializing mDNS controller"); - - let simple_poll = avahi_sys::avahi_simple_poll_new(); - let poll = avahi_sys::avahi_simple_poll_get(simple_poll); - let mut box_err = Box::pin(0 as i32); - let err_c: *mut i32 = box_err.as_mut().get_mut(); - let avahi_client = avahi_sys::avahi_client_new( - poll, - avahi_sys::AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, - Some(client_callback), - std::ptr::null_mut(), - err_c, - ); - if avahi_client == std::ptr::null_mut::() { - log_str_error("create Avahi client", *box_err); - panic!("Failed to create Avahi Client"); - } - let group = avahi_sys::avahi_entry_group_new( - avahi_client, - Some(entry_group_callback), - std::ptr::null_mut(), - ); - if group == std::ptr::null_mut() { - log_str_error("create Avahi entry group", avahi_client_errno(avahi_client)); - panic!("Failed to create Avahi Entry Group"); - } - let mut hostname_buf = vec![0]; - let hostname_raw = avahi_sys::avahi_client_get_host_name_fqdn(avahi_client); - hostname_buf - .extend_from_slice(std::ffi::CStr::from_ptr(hostname_raw).to_bytes_with_nul()); - let buflen = hostname_buf.len(); - debug_assert!(hostname_buf.ends_with(b".local\0")); - debug_assert!(!hostname_buf[..(buflen - 7)].contains(&b'.')); - // assume fixed length prefix on hostname due to local address - hostname_buf[0] = (buflen - 8) as u8; // set the prefix length to len - 8 (leading byte, .local, nul) for the main address - hostname_buf[buflen - 7] = 5; // set the prefix length to 5 for "local" - let mut res = MdnsEntryGroup { - hostname: hostname_buf, - hostname_raw, - entry_group: group, - _client_error: box_err, - }; - res.load_services(services); - let commit_err = avahi_entry_group_commit(res.entry_group); - if commit_err < avahi_sys::AVAHI_OK { - log_str_error("reset Avahi entry group", commit_err); - panic!("Failed to load Avahi services: reset"); - } - res - } - } -} -impl Drop for MdnsEntryGroup { - fn drop(&mut self) { - unsafe { - avahi_free(self.hostname_raw as *mut c_void); - avahi_entry_group_free(self.entry_group); - // avahi_client_free(self.avahi_client); - } - } -} - -unsafe extern "C" fn entry_group_callback( - _group: *mut avahi_sys::AvahiEntryGroup, - state: avahi_sys::AvahiEntryGroupState, - _userdata: *mut core::ffi::c_void, -) { - match state { - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_FAILURE => { - tracing::warn!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_FAILURE"); - } - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_COLLISION => { - tracing::warn!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_COLLISION"); - } - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_UNCOMMITED => { - tracing::warn!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_UNCOMMITED"); - } - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_ESTABLISHED => { - tracing::warn!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_ESTABLISHED"); - } - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_REGISTERING => { - tracing::warn!("AvahiCallback: EntryGroupState = AVAHI_ENTRY_GROUP_REGISTERING"); - } - other => { - tracing::warn!("AvahiCallback: EntryGroupState = {}", other); - } - } -} - -unsafe extern "C" fn client_callback( - _group: *mut avahi_sys::AvahiClient, - state: avahi_sys::AvahiClientState, - _userdata: *mut core::ffi::c_void, -) { - match state { - avahi_sys::AvahiClientState_AVAHI_CLIENT_FAILURE => { - tracing::warn!("AvahiCallback: ClientState = AVAHI_CLIENT_FAILURE"); - } - avahi_sys::AvahiClientState_AVAHI_CLIENT_S_RUNNING => { - tracing::warn!("AvahiCallback: ClientState = AVAHI_CLIENT_S_RUNNING"); - } - avahi_sys::AvahiClientState_AVAHI_CLIENT_CONNECTING => { - tracing::warn!("AvahiCallback: ClientState = AVAHI_CLIENT_CONNECTING"); - } - avahi_sys::AvahiClientState_AVAHI_CLIENT_S_COLLISION => { - tracing::warn!("AvahiCallback: ClientState = AVAHI_CLIENT_S_COLLISION"); - } - avahi_sys::AvahiClientState_AVAHI_CLIENT_S_REGISTERING => { - tracing::warn!("AvahiCallback: ClientState = AVAHI_CLIENT_S_REGISTERING"); - } - other => { - tracing::warn!("AvahiCallback: ClientState = {}", other); - } + self.sync().await?; + Ok(()) } } diff --git a/backend/src/net/mod.rs b/backend/src/net/mod.rs index a2ec1f5e8..d882500cb 100644 --- a/backend/src/net/mod.rs +++ b/backend/src/net/mod.rs @@ -70,7 +70,7 @@ impl NetController { Ok(Self { tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?, #[cfg(feature = "avahi")] - mdns: MdnsController::init(), + mdns: MdnsController::init().await?, nginx: NginxController::init(PathBuf::from("/etc/nginx"), &ssl, &hostname).await?, ssl, dns: DnsController::init(dns_bind).await?, diff --git a/build/write-image.sh b/build/write-image.sh index fc945dad8..1d1430dcf 100755 --- a/build/write-image.sh +++ b/build/write-image.sh @@ -63,6 +63,7 @@ cd backend/ sudo cp target/aarch64-unknown-linux-gnu/release/embassy-init /tmp/eos-mnt/usr/local/bin sudo cp target/aarch64-unknown-linux-gnu/release/embassyd /tmp/eos-mnt/usr/local/bin sudo cp target/aarch64-unknown-linux-gnu/release/embassy-cli /tmp/eos-mnt/usr/local/bin +sudo cp target/aarch64-unknown-linux-gnu/release/avahi-alias /tmp/eos-mnt/usr/local/bin sudo cp *.service /tmp/eos-mnt/etc/systemd/system/ cd ..