1use crate::{
5 ffi::apple::{
6 kDNSServiceErr_NoError, kDNSServiceErr_ServiceNotRunning, DNSServiceErrorType,
7 DNSServiceFlags, DNSServiceProcessResult, DNSServiceRef, DNSServiceRefDeallocate,
8 DNSServiceRefSockFD, DNSServiceRegister,
9 },
10 os::apple::txt::TXTRecord,
11 register::Result,
12 DNSServiceBuilder,
13};
14use std::{
15 ffi::{c_void, CStr, CString},
16 fmt,
17 os::raw::c_char,
18 ptr::{null, null_mut},
19 sync::{
20 atomic::{AtomicBool, Ordering},
21 mpsc::{sync_channel, SyncSender},
22 Arc,
23 },
24 time::Duration,
25};
26use thiserror::Error;
27
28const CALLBACK_TIMEOUT: Duration = Duration::from_secs(10);
29
30#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)]
32pub enum RegistrationError {
33 #[error("Invalid string argument, must be C string compatible")]
35 InvalidString,
36 #[error("DNSSD API returned invalid UTF-8 string")]
38 InternalInvalidString,
39 #[error("DNSSD Error: {0}")]
41 ServiceError(i32),
42}
43
44unsafe extern "C" fn register_reply(
45 _sd_ref: DNSServiceRef,
46 _flags: DNSServiceFlags,
47 error_code: DNSServiceErrorType,
48 name: *const c_char,
49 regtype: *const c_char,
50 domain: *const c_char,
51 context: *mut c_void,
52) {
53 info!("Got reply");
54 let process = || -> Result<(String, String, String)> {
56 let c_str: &CStr = CStr::from_ptr(name);
57 let service_name: &str = c_str
58 .to_str()
59 .map_err(|_| RegistrationError::InternalInvalidString)?;
60 let c_str: &CStr = CStr::from_ptr(regtype);
61 let regtype: &str = c_str
62 .to_str()
63 .map_err(|_| RegistrationError::InternalInvalidString)?;
64 let c_str: &CStr = CStr::from_ptr(domain);
65 let reply_domain: &str = c_str
66 .to_str()
67 .map_err(|_| RegistrationError::InternalInvalidString)?;
68 Ok((
69 service_name.to_owned(),
70 regtype.to_owned(),
71 reply_domain.to_owned(),
72 ))
73 };
74 if !context.is_null() {
75 let tx_ptr: *mut SyncSender<Result<DNSServiceRegisterReply>> = context as _;
76 let tx = &*tx_ptr;
77 trace!("Registration replied");
78 match process() {
79 Ok((name, regtype, domain)) => {
80 if error_code == kDNSServiceErr_NoError {
81 let reply = DNSServiceRegisterReply {
82 regtype,
83 name,
84 domain,
85 };
86 tx.send(Ok(reply)).unwrap();
87 info!("Reply info sent");
88 } else {
89 error!("Error in reply: {}", error_code);
90 tx.send(Err(RegistrationError::ServiceError(error_code)))
91 .unwrap();
92 }
93 }
94 Err(e) => {
95 error!("Error in reply: {:?}", e);
96 tx.send(Err(e)).unwrap();
97 }
98 }
99 }
100}
101
102pub struct RegisteredDnsService {
104 shutdown_flag: Arc<AtomicBool>,
105}
106impl fmt::Debug for RegisteredDnsService {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "RegisteredDnsService")
109 }
110}
111
112#[derive(Debug)]
114#[allow(dead_code)] pub struct DNSServiceRegisterReply {
116 pub regtype: String,
118 pub name: String,
120 pub domain: String,
122}
123
124struct ServiceRef {
126 raw: DNSServiceRef,
127 context: *mut c_void,
128}
129impl ServiceRef {
130 fn new(raw: DNSServiceRef, context: *mut c_void) -> Self {
131 ServiceRef { raw, context }
132 }
133}
134unsafe impl Send for ServiceRef {}
135impl Drop for ServiceRef {
136 fn drop(&mut self) {
137 unsafe {
138 trace!("Dropping service");
139 if !self.raw.is_null() {
140 trace!("Deallocating DNSServiceRef");
141 DNSServiceRefDeallocate(self.raw);
142 self.raw = null_mut();
143 _ = Box::from_raw(self.context);
144 }
145 }
146 }
147}
148
149impl RegisteredDnsService {}
150
151impl Drop for RegisteredDnsService {
153 fn drop(&mut self) {
154 trace!("Signaling thread to exit...");
155 self.shutdown_flag.store(true, Ordering::Relaxed);
156 }
157}
158fn run_thread_with_poll(service: ServiceRef, shutdown_flag: Arc<AtomicBool>) {
159 let socket = unsafe { DNSServiceRefSockFD(service.raw) };
160
161 std::thread::spawn(move || {
162 loop {
163 if shutdown_flag.load(Ordering::Relaxed) {
165 trace!("Shutdown requested, exiting DNS-SD processing thread");
166 break;
167 }
168
169 unsafe {
170 let mut poll_fd = libc::pollfd {
171 fd: socket,
172 events: libc::POLLIN,
173 revents: 0,
174 };
175
176 let result = libc::poll(&mut poll_fd, 1, 100);
178
179 match result {
180 -1 => {
181 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
183 if errno == libc::EINTR {
184 continue;
185 } else if errno == libc::EBADF {
186 trace!("DNS-SD socket closed, exiting thread");
187 break;
188 } else {
189 error!("poll() error: {}, exiting thread", errno);
190 break;
191 }
192 }
193 0 => {
194 continue;
196 }
197 _ => {
198 if poll_fd.revents & libc::POLLIN != 0 {
200 trace!("Processing DNS-SD result...");
202 let r = DNSServiceProcessResult(service.raw);
203 if r != kDNSServiceErr_NoError {
204 if r == kDNSServiceErr_ServiceNotRunning {
205 trace!("DNS-SD service stopped, exiting thread");
206 } else {
207 error!("Error processing DNS-SD result: {}, exiting thread", r);
208 }
209 break;
210 }
211 } else if poll_fd.revents & (libc::POLLHUP | libc::POLLERR | libc::POLLNVAL)
212 != 0
213 {
214 trace!(
216 "DNS-SD socket error/hangup (revents: {}), exiting thread",
217 poll_fd.revents
218 );
219 break;
220 }
221 }
222 }
223 }
224 }
225
226 trace!("DNS-SD processing thread exited");
227 });
228}
229
230pub fn register_service(service: DNSServiceBuilder) -> Result<RegisteredDnsService> {
231 unsafe {
232 let c_name: Option<CString>;
233 if let Some(n) = &service.name {
234 c_name = Some(CString::new(n.as_str()).map_err(|_| RegistrationError::InvalidString)?);
235 } else {
236 c_name = None;
237 }
238 let c_name = c_name.as_ref();
239 let service_type =
240 CString::new(service.regtype.as_str()).map_err(|_| RegistrationError::InvalidString)?;
241 let txt = service.txt.map(TXTRecord::from);
242 let (txt_record, txt_len) = match &txt {
243 Some(txt) => (txt.raw_bytes_ptr(), txt.raw_bytes_len()),
244 None => (null(), 0),
245 };
246
247 let (tx, rx) = sync_channel::<Result<DNSServiceRegisterReply>>(4);
248 let tx = Box::into_raw(Box::new(tx));
249
250 let mut raw: DNSServiceRef = null_mut();
251 let result = DNSServiceRegister(
252 &mut raw,
253 0,
254 0,
255 c_name.map_or(null_mut(), |c| c.as_ptr()),
256 service_type.as_ptr(),
257 null(),
258 null(),
259 service.port.to_be(),
260 txt_len,
261 txt_record,
262 Some(register_reply),
263 tx as _,
264 );
265 if result == kDNSServiceErr_NoError {
266 let shutdown_flag = Arc::new(AtomicBool::new(false));
268 let raw_service = ServiceRef::new(raw, tx as _);
269
270 run_thread_with_poll(raw_service, shutdown_flag.clone());
272
273 let service = RegisteredDnsService { shutdown_flag };
274
275 match rx.recv_timeout(CALLBACK_TIMEOUT) {
276 Ok(Ok(_reply)) => Ok(service),
277 Ok(Err(e)) => Err(e),
278 Err(e) => {
279 error!("Error waiting for callback: {:?}", e);
280 Err(RegistrationError::ServiceError(0))
281 }
282 }
283 } else {
284 Err(RegistrationError::ServiceError(result))
285 }
286 }
287}