1use std::collections::HashMap;
8use std::net::IpAddr;
9use std::path::PathBuf;
10use std::sync::Arc;
11use std::time::Duration;
12
13use tokio::net::TcpStream;
14use tokio::sync::{broadcast, mpsc, oneshot, Mutex, RwLock};
15use tokio::task::JoinHandle;
16
17use super::bridge::{Bridge, DIAL_TIMEOUT};
18use super::sidecar::{GoSidecar, SidecarConfig, SidecarInternalEvent};
19use crate::network::{
20 HealthInfo, IncomingConnection, NetworkError, NetworkPeer, NetworkPeerEvent,
21 NetworkTcpListener, NodeIdentity, PeerAddr, PingResult,
22};
23
24#[derive(Debug, Clone)]
26pub struct TailscaleConfig {
27 pub binary_path: PathBuf,
29 pub app_id: String,
33 pub device_id: String,
35 pub device_name: String,
38 pub hostname: String,
42 pub state_dir: String,
44 pub auth_key: Option<String>,
46 pub ephemeral: Option<bool>,
48 pub tags: Option<Vec<String>>,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54enum ProviderState {
55 Stopped,
56 Starting,
57 Running,
58 Stopping,
59}
60
61pub struct TailscaleProvider {
72 config: TailscaleConfig,
73 state: Arc<RwLock<ProviderState>>,
74
75 identity: Arc<std::sync::RwLock<NodeIdentity>>,
80 local_addr: Arc<std::sync::RwLock<PeerAddr>>,
85
86 peers: Arc<RwLock<HashMap<String, NetworkPeer>>>,
88
89 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
91
92 health: Arc<RwLock<HealthInfo>>,
94
95 sidecar: Arc<Mutex<Option<GoSidecar>>>,
97
98 bridge: Arc<Mutex<Option<Arc<Bridge>>>>,
100
101 bridge_shutdown_tx: Arc<Mutex<Option<tokio::sync::watch::Sender<bool>>>>,
103
104 session_token: Arc<RwLock<[u8; 32]>>,
106
107 local_tailscale_id: Arc<std::sync::RwLock<Option<String>>>,
113}
114
115impl TailscaleProvider {
116 pub fn new(config: TailscaleConfig) -> Self {
120 let (peer_event_tx, _) = broadcast::channel(256);
121
122 let initial_identity = NodeIdentity {
126 app_id: config.app_id.clone(),
127 device_id: config.device_id.clone(),
128 device_name: config.device_name.clone(),
129 tailscale_hostname: config.hostname.clone(),
130 tailscale_id: String::new(),
131 dns_name: None,
132 ip: None,
133 };
134
135 Self {
136 config,
137 state: Arc::new(RwLock::new(ProviderState::Stopped)),
138 identity: Arc::new(std::sync::RwLock::new(initial_identity)),
139 local_addr: Arc::new(std::sync::RwLock::new(PeerAddr::default())),
140 peers: Arc::new(RwLock::new(HashMap::new())),
141 peer_event_tx,
142 health: Arc::new(RwLock::new(HealthInfo {
143 state: "stopped".to_string(),
144 healthy: false,
145 ..Default::default()
146 })),
147 sidecar: Arc::new(Mutex::new(None)),
148 bridge: Arc::new(Mutex::new(None)),
149 bridge_shutdown_tx: Arc::new(Mutex::new(None)),
150 session_token: Arc::new(RwLock::new([0u8; 32])),
151 local_tailscale_id: Arc::new(std::sync::RwLock::new(None)),
152 }
153 }
154
155 fn generate_session_token() -> Result<[u8; 32], NetworkError> {
157 let mut token = [0u8; 32];
158 getrandom::getrandom(&mut token).map_err(|e| {
159 NetworkError::Internal(format!("failed to generate session token: {e}"))
160 })?;
161 Ok(token)
162 }
163
164 fn sidecar_peer_to_network_peer(peer: &super::protocol::SidecarPeer) -> NetworkPeer {
166 let ip = peer
167 .tailscale_ips
168 .first()
169 .and_then(|s| s.parse::<IpAddr>().ok())
170 .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
171
172 NetworkPeer {
173 id: peer.id.clone(),
174 hostname: peer.hostname.clone(),
175 ip,
176 online: peer.online,
177 cur_addr: if peer.cur_addr.is_empty() {
178 None
179 } else {
180 Some(peer.cur_addr.clone())
181 },
182 relay: if peer.relay.is_empty() {
183 None
184 } else {
185 Some(peer.relay.clone())
186 },
187 os: if peer.os.is_empty() {
188 None
189 } else {
190 Some(peer.os.clone())
191 },
192 last_seen: peer.last_seen.clone(),
193 key_expiry: peer.key_expiry.clone(),
194 dns_name: Some(peer.dns_name.clone()),
195 }
196 }
197
198 #[allow(clippy::too_many_arguments)]
201 fn spawn_event_processor(
202 mut sidecar_rx: broadcast::Receiver<SidecarInternalEvent>,
203 peers: Arc<RwLock<HashMap<String, NetworkPeer>>>,
204 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
205 health: Arc<RwLock<HealthInfo>>,
206 identity: Arc<std::sync::RwLock<NodeIdentity>>,
207 local_addr: Arc<std::sync::RwLock<PeerAddr>>,
208 local_tailscale_id: Arc<std::sync::RwLock<Option<String>>>,
209 state: Arc<RwLock<ProviderState>>,
210 started_tx: Option<oneshot::Sender<Result<(), NetworkError>>>,
211 app_id: String,
212 ) {
213 tokio::spawn(async move {
214 let mut started_tx = started_tx;
215
216 loop {
217 match sidecar_rx.recv().await {
218 Ok(event) => {
219 match event {
220 SidecarInternalEvent::Started {
221 hostname,
222 dns_name,
223 tailscale_ip,
224 node_id,
225 } => {
226 let ip: Option<IpAddr> = tailscale_ip.parse().ok();
227
228 {
229 let mut id = identity.write().unwrap();
230 id.tailscale_hostname = hostname.clone();
236 id.dns_name = Some(dns_name.clone());
237 id.ip = ip;
238 if !node_id.is_empty() {
239 id.tailscale_id = node_id.clone();
240 }
241 }
242
243 if !node_id.is_empty() {
246 *local_tailscale_id.write().unwrap() = Some(node_id);
247 }
248
249 {
250 let mut addr = local_addr.write().unwrap();
251 addr.hostname = hostname;
252 addr.dns_name = Some(dns_name);
253 addr.ip = ip;
254 }
255
256 {
257 let mut h = health.write().await;
258 h.state = "running".to_string();
259 h.healthy = true;
260 }
261
262 *state.write().await = ProviderState::Running;
263
264 if let Some(tx) = started_tx.take() {
266 let _ = tx.send(Ok(()));
267 }
268 }
269 SidecarInternalEvent::AuthRequired { auth_url } => {
270 tracing::info!("tailscale auth required: {auth_url}");
271 let _ = peer_event_tx
274 .send(NetworkPeerEvent::AuthRequired { url: auth_url });
275 }
276 SidecarInternalEvent::Stopped => {
277 *state.write().await = ProviderState::Stopped;
278 let mut h = health.write().await;
279 h.state = "stopped".to_string();
280 h.healthy = false;
281 tracing::info!("tailscale provider stopped");
282 return;
283 }
284 SidecarInternalEvent::StateChange { state: new_state } => {
285 let mut h = health.write().await;
286 h.state = new_state;
287 }
288 SidecarInternalEvent::KeyExpiring { expires_at } => {
289 let mut h = health.write().await;
290 h.key_expiry = Some(expires_at);
291 }
292 SidecarInternalEvent::HealthWarning { warnings } => {
293 let mut h = health.write().await;
294 h.warnings = warnings;
295 h.healthy = h.warnings.is_empty();
296 }
297 SidecarInternalEvent::PeersReceived(sidecar_peers) => {
298 let mut peer_map = peers.write().await;
299 let self_id = local_tailscale_id.read().unwrap().clone();
305 let new_peers: HashMap<String, NetworkPeer> = sidecar_peers
308 .iter()
309 .filter(|p| {
310 if let Some(ref me) = self_id {
311 if p.id == *me {
312 return false;
313 }
314 }
315 is_app_peer(&p.hostname, &app_id)
316 })
317 .map(|p| {
318 let np = Self::sidecar_peer_to_network_peer(p);
319 (np.id.clone(), np)
320 })
321 .collect();
322
323 for (id, new_peer) in &new_peers {
325 if let Some(_existing) = peer_map.get(id) {
326 let _ = peer_event_tx
327 .send(NetworkPeerEvent::Updated(new_peer.clone()));
328 } else {
329 let _ = peer_event_tx
330 .send(NetworkPeerEvent::Joined(new_peer.clone()));
331 }
332 }
333 for id in peer_map.keys() {
334 if !new_peers.contains_key(id) {
335 let _ =
336 peer_event_tx.send(NetworkPeerEvent::Left(id.clone()));
337 }
338 }
339
340 *peer_map = new_peers;
341 }
342 SidecarInternalEvent::PeerChanged(change) => {
343 let mut peer_map = peers.write().await;
344 let self_id = local_tailscale_id.read().unwrap().clone();
347 match change.change_type.as_str() {
348 "joined" => {
349 if let Some(p) = change.peer {
350 if let Some(ref me) = self_id {
351 if p.id == *me {
352 continue;
353 }
354 }
355 if is_app_peer(&p.hostname, &app_id) {
356 let np = Self::sidecar_peer_to_network_peer(&p);
357 peer_map.insert(np.id.clone(), np.clone());
358 let _ = peer_event_tx
359 .send(NetworkPeerEvent::Joined(np));
360 }
361 }
362 }
363 "left" => {
364 if peer_map.remove(&change.peer_id).is_some() {
365 let _ = peer_event_tx
366 .send(NetworkPeerEvent::Left(change.peer_id));
367 }
368 }
369 "updated" => {
370 if let Some(p) = change.peer {
371 if let Some(ref me) = self_id {
372 if p.id == *me {
373 continue;
374 }
375 }
376 if is_app_peer(&p.hostname, &app_id) {
377 let np = Self::sidecar_peer_to_network_peer(&p);
378 peer_map.insert(np.id.clone(), np.clone());
379 let _ = peer_event_tx
380 .send(NetworkPeerEvent::Updated(np));
381 }
382 }
383 }
384 other => {
385 tracing::warn!("unknown peer change type: {other}");
386 }
387 }
388 }
389 SidecarInternalEvent::Error { code, message } => {
390 tracing::error!("sidecar error [{code}]: {message}");
391 if let Some(tx) = started_tx.take() {
393 let _ = tx.send(Err(NetworkError::SidecarError(format!(
394 "[{code}] {message}"
395 ))));
396 }
397 }
398 SidecarInternalEvent::ProcessExited { exit_code } => {
399 tracing::error!("sidecar process exited: {exit_code:?}");
400 *state.write().await = ProviderState::Stopped;
401 let mut h = health.write().await;
402 h.state = "crashed".to_string();
403 h.healthy = false;
404 if let Some(tx) = started_tx.take() {
405 let _ = tx.send(Err(NetworkError::SidecarError(format!(
406 "process exited with code {exit_code:?}"
407 ))));
408 }
409 return;
410 }
411 _ => {}
414 }
415 }
416 Err(broadcast::error::RecvError::Lagged(n)) => {
417 tracing::warn!("event processor lagged by {n} events");
418 }
419 Err(broadcast::error::RecvError::Closed) => {
420 tracing::info!("sidecar event channel closed, stopping event processor");
421 return;
422 }
423 }
424 }
425 });
426 }
427}
428
429pub(crate) fn is_app_peer(hostname: &str, app_id: &str) -> bool {
439 let prefix = format!("truffle-{app_id}-");
440 hostname.len() > prefix.len() && hostname.starts_with(&prefix)
441}
442
443impl super::super::NetworkProvider for TailscaleProvider {
444 async fn start(&mut self) -> Result<(), NetworkError> {
445 {
446 let current_state = *self.state.read().await;
447 if current_state != ProviderState::Stopped {
448 return Err(NetworkError::AlreadyRunning);
449 }
450 }
451 *self.state.write().await = ProviderState::Starting;
452
453 let token = Self::generate_session_token()?;
455 let token_hex = hex::encode(token);
456 *self.session_token.write().await = token;
457
458 let bridge = Bridge::bind(token).await?;
460 let bridge_port = bridge.local_port()?;
461 let bridge = Arc::new(bridge);
462
463 let (bridge_shutdown_tx, bridge_shutdown_rx) = tokio::sync::watch::channel(false);
465
466 {
468 let bridge_clone = bridge.clone();
469 tokio::spawn(async move {
470 bridge_clone.run(bridge_shutdown_rx).await;
471 });
472 }
473
474 *self.bridge.lock().await = Some(bridge.clone());
475 *self.bridge_shutdown_tx.lock().await = Some(bridge_shutdown_tx);
476
477 let sidecar_config = SidecarConfig {
479 binary_path: self.config.binary_path.clone(),
480 hostname: self.config.hostname.clone(),
481 state_dir: self.config.state_dir.clone(),
482 auth_key: self.config.auth_key.clone(),
483 bridge_port,
484 session_token_hex: token_hex,
485 ephemeral: self.config.ephemeral,
486 tags: self.config.tags.clone(),
487 };
488
489 let (sidecar, sidecar_rx) = GoSidecar::spawn(sidecar_config.clone()).await?;
491
492 let (started_tx, started_rx) = oneshot::channel();
494
495 Self::spawn_event_processor(
497 sidecar_rx,
498 self.peers.clone(),
499 self.peer_event_tx.clone(),
500 self.health.clone(),
501 self.identity.clone(),
502 self.local_addr.clone(),
503 self.local_tailscale_id.clone(),
504 self.state.clone(),
505 Some(started_tx),
506 self.config.app_id.clone(),
507 );
508
509 sidecar.send_start(&sidecar_config).await?;
511
512 *self.sidecar.lock().await = Some(sidecar);
513
514 let auth_timeout = Duration::from_secs(300);
518 let result = tokio::time::timeout(auth_timeout, started_rx)
519 .await
520 .map_err(|_| {
521 NetworkError::StartFailed(
522 "timed out waiting for authentication (5 min). \
523 Subscribe to peer_events() to display auth URLs."
524 .into(),
525 )
526 })?
527 .map_err(|_| NetworkError::StartFailed("start signal channel dropped".into()))?;
528
529 match result {
530 Ok(()) => {
531 if let Some(ref sidecar) = *self.sidecar.lock().await {
533 let _ = sidecar.send_get_peers().await;
534 let _ = sidecar.send_watch_peers().await;
536 }
537 tracing::info!("tailscale provider started successfully");
538 Ok(())
539 }
540 Err(e) => {
541 *self.state.write().await = ProviderState::Stopped;
542 Err(e)
543 }
544 }
545 }
546
547 async fn stop(&mut self) -> Result<(), NetworkError> {
548 *self.state.write().await = ProviderState::Stopping;
549
550 if let Some(sidecar) = self.sidecar.lock().await.take() {
552 sidecar.shutdown().await;
553 }
554
555 if let Some(tx) = self.bridge_shutdown_tx.lock().await.take() {
557 let _ = tx.send(true);
558 }
559 *self.bridge.lock().await = None;
560
561 self.peers.write().await.clear();
563 *self.state.write().await = ProviderState::Stopped;
564 let mut h = self.health.write().await;
565 h.state = "stopped".to_string();
566 h.healthy = false;
567
568 tracing::info!("tailscale provider stopped");
569 Ok(())
570 }
571
572 fn local_identity(&self) -> NodeIdentity {
573 self.identity.read().unwrap().clone()
574 }
575
576 fn local_addr(&self) -> PeerAddr {
577 self.local_addr.read().unwrap().clone()
578 }
579
580 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent> {
581 self.peer_event_tx.subscribe()
582 }
583
584 async fn peers(&self) -> Vec<NetworkPeer> {
585 self.peers.read().await.values().cloned().collect()
586 }
587
588 async fn dial_tcp(&self, addr: &str, port: u16) -> Result<TcpStream, NetworkError> {
589 if *self.state.read().await != ProviderState::Running {
590 return Err(NetworkError::NotRunning);
591 }
592
593 let bridge = self
594 .bridge
595 .lock()
596 .await
597 .clone()
598 .ok_or(NetworkError::NotRunning)?;
599
600 let request_id = uuid::Uuid::new_v4().to_string();
602
603 let dial_rx = bridge.register_dial(request_id.clone()).await;
605
606 let mut event_rx = {
608 let sidecar_guard = self.sidecar.lock().await;
609 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
610
611 let event_rx = sidecar.subscribe();
612
613 sidecar
614 .send_dial(request_id.clone(), addr.to_string(), port)
615 .await?;
616
617 event_rx
618 };
619
620 let result = tokio::time::timeout(DIAL_TIMEOUT, async {
629 let fail_request_id = request_id.clone();
633 let (fail_tx, fail_rx) = oneshot::channel::<String>();
634 let fail_watcher: JoinHandle<()> = tokio::spawn(async move {
635 loop {
636 match event_rx.recv().await {
637 Ok(SidecarInternalEvent::DialFailed {
638 request_id: rid,
639 error,
640 }) if rid == fail_request_id => {
641 let _ = fail_tx.send(error);
642 return;
643 }
644 Err(broadcast::error::RecvError::Closed) => {
645 let _ = fail_tx.send("event channel closed".to_string());
646 return;
647 }
648 _ => continue,
649 }
650 }
651 });
652
653 let result = tokio::select! {
654 stream_result = dial_rx => {
655 stream_result.map_err(|_| NetworkError::DialFailed("dial cancelled".into()))
656 }
657 fail_result = fail_rx => {
658 let error = fail_result.unwrap_or_else(|_| "dial watcher dropped".to_string());
659 Err(NetworkError::DialFailed(error))
660 }
661 };
662
663 fail_watcher.abort();
665
666 result
667 })
668 .await
669 .map_err(|_| NetworkError::DialTimeout(DIAL_TIMEOUT))?;
670
671 if result.is_err() {
673 bridge.remove_dial(&request_id).await;
674 }
675
676 result
677 }
678
679 async fn listen_tcp(&self, port: u16) -> Result<NetworkTcpListener, NetworkError> {
680 if *self.state.read().await != ProviderState::Running {
681 return Err(NetworkError::NotRunning);
682 }
683
684 let bridge = self
685 .bridge
686 .lock()
687 .await
688 .clone()
689 .ok_or(NetworkError::NotRunning)?;
690
691 let (tx, rx) = mpsc::channel::<IncomingConnection>(64);
693
694 let mut event_rx = {
696 let sidecar_guard = self.sidecar.lock().await;
697 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
698
699 let event_rx = sidecar.subscribe();
700 sidecar.send_listen(port, None).await?;
701 event_rx
702 };
703
704 let actual_port = tokio::time::timeout(Duration::from_secs(10), async {
708 loop {
709 match event_rx.recv().await {
710 Ok(SidecarInternalEvent::Listening { port: p }) if port == 0 || p == port => {
711 return Ok(p);
712 }
713 Ok(SidecarInternalEvent::Error { code, message }) => {
714 return Err(NetworkError::ListenFailed(format!("[{code}] {message}")));
715 }
716 Err(broadcast::error::RecvError::Closed) => {
717 return Err(NetworkError::SidecarError("event channel closed".into()));
718 }
719 _ => continue,
720 }
721 }
722 })
723 .await
724 .map_err(|_| NetworkError::ListenFailed("listen confirmation timed out".into()))??;
725
726 bridge.register_listener(actual_port, tx).await;
728
729 Ok(NetworkTcpListener {
730 port: actual_port,
731 incoming: rx,
732 })
733 }
734
735 async fn unlisten_tcp(&self, port: u16) -> Result<(), NetworkError> {
736 if *self.state.read().await != ProviderState::Running {
737 return Err(NetworkError::NotRunning);
738 }
739
740 let bridge = self
741 .bridge
742 .lock()
743 .await
744 .clone()
745 .ok_or(NetworkError::NotRunning)?;
746
747 bridge.remove_listener(port).await;
749
750 {
752 let sidecar_guard = self.sidecar.lock().await;
753 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
754 sidecar.send_unlisten(port).await?;
755 }
756
757 Ok(())
758 }
759
760 async fn ping(&self, addr: &str) -> Result<PingResult, NetworkError> {
761 if *self.state.read().await != ProviderState::Running {
762 return Err(NetworkError::NotRunning);
763 }
764
765 let target = addr.to_string();
766
767 let mut event_rx = {
769 let sidecar_guard = self.sidecar.lock().await;
770 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
771
772 let event_rx = sidecar.subscribe();
773 sidecar.send_ping(target.clone(), None).await?;
774 event_rx
775 };
776
777 let result = tokio::time::timeout(Duration::from_secs(15), async {
779 loop {
780 match event_rx.recv().await {
781 Ok(SidecarInternalEvent::PingResult(data)) if data.target == target => {
782 if !data.error.is_empty() {
783 return Err(NetworkError::PingFailed(data.error));
784 }
785 let connection = if data.direct {
786 "direct".to_string()
787 } else if !data.relay.is_empty() {
788 format!("relay:{}", data.relay)
789 } else {
790 "unknown".to_string()
791 };
792 return Ok(PingResult {
793 latency: Duration::from_secs_f64(data.latency_ms / 1000.0),
794 connection,
795 peer_addr: if data.peer_addr.is_empty() {
796 None
797 } else {
798 Some(data.peer_addr)
799 },
800 });
801 }
802 Err(broadcast::error::RecvError::Closed) => {
803 return Err(NetworkError::SidecarError("event channel closed".into()));
804 }
805 _ => continue,
806 }
807 }
808 })
809 .await
810 .map_err(|_| NetworkError::PingFailed("ping timed out".into()))?;
811
812 result
813 }
814
815 async fn bind_udp(&self, port: u16) -> Result<super::super::NetworkUdpSocket, NetworkError> {
816 if *self.state.read().await != ProviderState::Running {
817 return Err(NetworkError::NotRunning);
818 }
819
820 let mut event_rx = {
822 let sidecar_guard = self.sidecar.lock().await;
823 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
824
825 let event_rx = sidecar.subscribe();
826 sidecar.send_listen_packet(port).await?;
827 event_rx
828 };
829
830 let local_port = tokio::time::timeout(Duration::from_secs(10), async {
832 loop {
833 match event_rx.recv().await {
834 Ok(SidecarInternalEvent::ListeningPacket {
835 port: p,
836 local_port,
837 }) if p == port => {
838 return Ok(local_port);
839 }
840 Ok(SidecarInternalEvent::Error { code, message }) => {
841 return Err(NetworkError::ListenFailed(format!(
842 "UDP bind failed [{code}] {message}"
843 )));
844 }
845 Err(broadcast::error::RecvError::Closed) => {
846 return Err(NetworkError::SidecarError("event channel closed".into()));
847 }
848 _ => continue,
849 }
850 }
851 })
852 .await
853 .map_err(|_| {
854 NetworkError::ListenFailed("UDP listenPacket confirmation timed out".into())
855 })??;
856
857 let local_socket = tokio::net::UdpSocket::bind("127.0.0.1:0")
859 .await
860 .map_err(|e| NetworkError::Internal(format!("failed to bind local UDP socket: {e}")))?;
861
862 local_socket
863 .connect(format!("127.0.0.1:{local_port}"))
864 .await
865 .map_err(|e| {
866 NetworkError::Internal(format!("failed to connect local UDP socket to relay: {e}"))
867 })?;
868
869 let rust_local_addr = local_socket
870 .local_addr()
871 .map_err(|e| NetworkError::Internal(format!("failed to get local UDP addr: {e}")))?;
872
873 local_socket
878 .send(b"TRUFFLE_UDP_REGISTER")
879 .await
880 .map_err(|e| NetworkError::Internal(format!("failed to send UDP registration: {e}")))?;
881
882 tracing::info!(
883 tsnet_port = port,
884 relay_port = local_port,
885 rust_local_addr = %rust_local_addr,
886 "UDP socket bound via tsnet relay (registered)"
887 );
888
889 Ok(super::super::NetworkUdpSocket::new(local_socket, port))
890 }
891
892 async fn health(&self) -> HealthInfo {
893 self.health.read().await.clone()
894 }
895}
896
897impl TailscaleProvider {
898 pub async fn local_identity_async(&self) -> NodeIdentity {
903 self.identity.read().unwrap().clone()
904 }
905
906 pub async fn local_addr_async(&self) -> PeerAddr {
911 self.local_addr.read().unwrap().clone()
912 }
913}