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::protocol::ProxyAddCommandData;
19use super::sidecar::{GoSidecar, SidecarConfig, SidecarInternalEvent};
20use crate::network::{
21 DialOpts, HealthInfo, IncomingConnection, NetworkError, NetworkPeer, NetworkPeerEvent,
22 NetworkTcpListener, NodeIdentity, PeerAddr, PingResult, ProxyAddParams, ProxyAddResult,
23 ProxyListEntry,
24};
25
26#[derive(Debug, Clone)]
28pub struct TailscaleConfig {
29 pub binary_path: PathBuf,
31 pub app_id: String,
35 pub device_id: String,
37 pub device_name: String,
40 pub hostname: String,
44 pub state_dir: String,
46 pub auth_key: Option<String>,
48 pub ephemeral: Option<bool>,
50 pub tags: Option<Vec<String>>,
52 pub idle_timeout_secs: Option<u64>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59enum ProviderState {
60 Stopped,
61 Starting,
62 Running,
63 Stopping,
64}
65
66pub struct TailscaleProvider {
77 config: TailscaleConfig,
78 state: Arc<RwLock<ProviderState>>,
79
80 identity: Arc<std::sync::RwLock<NodeIdentity>>,
85 local_addr: Arc<std::sync::RwLock<PeerAddr>>,
90
91 peers: Arc<RwLock<HashMap<String, NetworkPeer>>>,
93
94 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
96
97 health: Arc<RwLock<HealthInfo>>,
99
100 sidecar: Arc<Mutex<Option<GoSidecar>>>,
102
103 bridge: Arc<Mutex<Option<Arc<Bridge>>>>,
105
106 bridge_shutdown_tx: Arc<Mutex<Option<tokio::sync::watch::Sender<bool>>>>,
108
109 session_token: Arc<RwLock<[u8; 32]>>,
111
112 local_tailscale_id: Arc<std::sync::RwLock<Option<String>>>,
118}
119
120impl TailscaleProvider {
121 pub fn new(config: TailscaleConfig) -> Self {
125 let (peer_event_tx, _) = broadcast::channel(256);
126
127 let initial_identity = NodeIdentity {
131 app_id: config.app_id.clone(),
132 device_id: config.device_id.clone(),
133 device_name: config.device_name.clone(),
134 tailscale_hostname: config.hostname.clone(),
135 tailscale_id: String::new(),
136 dns_name: None,
137 ip: None,
138 };
139
140 Self {
141 config,
142 state: Arc::new(RwLock::new(ProviderState::Stopped)),
143 identity: Arc::new(std::sync::RwLock::new(initial_identity)),
144 local_addr: Arc::new(std::sync::RwLock::new(PeerAddr::default())),
145 peers: Arc::new(RwLock::new(HashMap::new())),
146 peer_event_tx,
147 health: Arc::new(RwLock::new(HealthInfo {
148 state: "stopped".to_string(),
149 healthy: false,
150 ..Default::default()
151 })),
152 sidecar: Arc::new(Mutex::new(None)),
153 bridge: Arc::new(Mutex::new(None)),
154 bridge_shutdown_tx: Arc::new(Mutex::new(None)),
155 session_token: Arc::new(RwLock::new([0u8; 32])),
156 local_tailscale_id: Arc::new(std::sync::RwLock::new(None)),
157 }
158 }
159
160 fn generate_session_token() -> Result<[u8; 32], NetworkError> {
162 let mut token = [0u8; 32];
163 getrandom::getrandom(&mut token).map_err(|e| {
164 NetworkError::Internal(format!("failed to generate session token: {e}"))
165 })?;
166 Ok(token)
167 }
168
169 fn sidecar_peer_to_network_peer(peer: &super::protocol::SidecarPeer) -> NetworkPeer {
171 let ip = peer
172 .tailscale_ips
173 .first()
174 .and_then(|s| s.parse::<IpAddr>().ok())
175 .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
176
177 NetworkPeer {
178 id: peer.id.clone(),
179 hostname: peer.hostname.clone(),
180 ip,
181 online: peer.online,
182 cur_addr: if peer.cur_addr.is_empty() {
183 None
184 } else {
185 Some(peer.cur_addr.clone())
186 },
187 relay: if peer.relay.is_empty() {
188 None
189 } else {
190 Some(peer.relay.clone())
191 },
192 os: if peer.os.is_empty() {
193 None
194 } else {
195 Some(peer.os.clone())
196 },
197 last_seen: peer.last_seen.clone(),
198 key_expiry: peer.key_expiry.clone(),
199 dns_name: Some(peer.dns_name.clone()),
200 }
201 }
202
203 #[allow(clippy::too_many_arguments)]
206 fn spawn_event_processor(
207 mut sidecar_rx: broadcast::Receiver<SidecarInternalEvent>,
208 peers: Arc<RwLock<HashMap<String, NetworkPeer>>>,
209 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
210 health: Arc<RwLock<HealthInfo>>,
211 identity: Arc<std::sync::RwLock<NodeIdentity>>,
212 local_addr: Arc<std::sync::RwLock<PeerAddr>>,
213 local_tailscale_id: Arc<std::sync::RwLock<Option<String>>>,
214 state: Arc<RwLock<ProviderState>>,
215 started_tx: Option<oneshot::Sender<Result<(), NetworkError>>>,
216 app_id: String,
217 ) {
218 tokio::spawn(async move {
219 let mut started_tx = started_tx;
220
221 loop {
222 match sidecar_rx.recv().await {
223 Ok(event) => {
224 match event {
225 SidecarInternalEvent::Started {
226 hostname,
227 dns_name,
228 tailscale_ip,
229 node_id,
230 } => {
231 let ip: Option<IpAddr> = tailscale_ip.parse().ok();
232
233 {
234 let mut id = identity.write().unwrap();
235 id.tailscale_hostname = hostname.clone();
241 id.dns_name = Some(dns_name.clone());
242 id.ip = ip;
243 if !node_id.is_empty() {
244 id.tailscale_id = node_id.clone();
245 }
246 }
247
248 if !node_id.is_empty() {
251 *local_tailscale_id.write().unwrap() = Some(node_id);
252 }
253
254 {
255 let mut addr = local_addr.write().unwrap();
256 addr.hostname = hostname;
257 addr.dns_name = Some(dns_name);
258 addr.ip = ip;
259 }
260
261 {
262 let mut h = health.write().await;
263 h.state = "running".to_string();
264 h.healthy = true;
265 }
266
267 *state.write().await = ProviderState::Running;
268
269 if let Some(tx) = started_tx.take() {
271 let _ = tx.send(Ok(()));
272 }
273 }
274 SidecarInternalEvent::AuthRequired { auth_url } => {
275 tracing::info!("tailscale auth required: {auth_url}");
276 let _ = peer_event_tx
279 .send(NetworkPeerEvent::AuthRequired { url: auth_url });
280 }
281 SidecarInternalEvent::Stopped => {
282 *state.write().await = ProviderState::Stopped;
283 let mut h = health.write().await;
284 h.state = "stopped".to_string();
285 h.healthy = false;
286 tracing::info!("tailscale provider stopped");
287 return;
288 }
289 SidecarInternalEvent::StateChange { state: new_state } => {
290 let mut h = health.write().await;
291 h.state = new_state;
292 }
293 SidecarInternalEvent::KeyExpiring { expires_at } => {
294 let mut h = health.write().await;
295 h.key_expiry = Some(expires_at);
296 }
297 SidecarInternalEvent::HealthWarning { warnings } => {
298 let mut h = health.write().await;
299 h.warnings = warnings;
300 h.healthy = h.warnings.is_empty();
301 }
302 SidecarInternalEvent::PeersReceived(sidecar_peers) => {
303 let mut peer_map = peers.write().await;
304 let self_id = local_tailscale_id.read().unwrap().clone();
310 let new_peers: HashMap<String, NetworkPeer> = sidecar_peers
313 .iter()
314 .filter(|p| {
315 if let Some(ref me) = self_id {
316 if p.id == *me {
317 return false;
318 }
319 }
320 is_app_peer(&p.hostname, &app_id)
321 })
322 .map(|p| {
323 let np = Self::sidecar_peer_to_network_peer(p);
324 (np.id.clone(), np)
325 })
326 .collect();
327
328 for (id, new_peer) in &new_peers {
330 if let Some(_existing) = peer_map.get(id) {
331 let _ = peer_event_tx
332 .send(NetworkPeerEvent::Updated(new_peer.clone()));
333 } else {
334 let _ = peer_event_tx
335 .send(NetworkPeerEvent::Joined(new_peer.clone()));
336 }
337 }
338 for id in peer_map.keys() {
339 if !new_peers.contains_key(id) {
340 let _ =
341 peer_event_tx.send(NetworkPeerEvent::Left(id.clone()));
342 }
343 }
344
345 *peer_map = new_peers;
346 }
347 SidecarInternalEvent::PeerChanged(change) => {
348 let mut peer_map = peers.write().await;
349 let self_id = local_tailscale_id.read().unwrap().clone();
352 match change.change_type.as_str() {
353 "joined" => {
354 if let Some(p) = change.peer {
355 if let Some(ref me) = self_id {
356 if p.id == *me {
357 continue;
358 }
359 }
360 if is_app_peer(&p.hostname, &app_id) {
361 let np = Self::sidecar_peer_to_network_peer(&p);
362 peer_map.insert(np.id.clone(), np.clone());
363 let _ = peer_event_tx
364 .send(NetworkPeerEvent::Joined(np));
365 }
366 }
367 }
368 "left" => {
369 if peer_map.remove(&change.peer_id).is_some() {
370 let _ = peer_event_tx
371 .send(NetworkPeerEvent::Left(change.peer_id));
372 }
373 }
374 "updated" => {
375 if let Some(p) = change.peer {
376 if let Some(ref me) = self_id {
377 if p.id == *me {
378 continue;
379 }
380 }
381 if is_app_peer(&p.hostname, &app_id) {
382 let np = Self::sidecar_peer_to_network_peer(&p);
383 peer_map.insert(np.id.clone(), np.clone());
384 let _ = peer_event_tx
385 .send(NetworkPeerEvent::Updated(np));
386 }
387 }
388 }
389 other => {
390 tracing::warn!("unknown peer change type: {other}");
391 }
392 }
393 }
394 SidecarInternalEvent::Error { code, message } => {
395 tracing::error!("sidecar error [{code}]: {message}");
396 if let Some(tx) = started_tx.take() {
398 let _ = tx.send(Err(NetworkError::SidecarError(format!(
399 "[{code}] {message}"
400 ))));
401 }
402 }
403 SidecarInternalEvent::ProcessExited { exit_code } => {
404 tracing::error!("sidecar process exited: {exit_code:?}");
405 *state.write().await = ProviderState::Stopped;
406 let mut h = health.write().await;
407 h.state = "crashed".to_string();
408 h.healthy = false;
409 if let Some(tx) = started_tx.take() {
410 let _ = tx.send(Err(NetworkError::SidecarError(format!(
411 "process exited with code {exit_code:?}"
412 ))));
413 }
414 return;
415 }
416 _ => {}
419 }
420 }
421 Err(broadcast::error::RecvError::Lagged(n)) => {
422 tracing::warn!("event processor lagged by {n} events");
423 }
424 Err(broadcast::error::RecvError::Closed) => {
425 tracing::info!("sidecar event channel closed, stopping event processor");
426 return;
427 }
428 }
429 }
430 });
431 }
432}
433
434pub(crate) fn is_app_peer(hostname: &str, app_id: &str) -> bool {
444 let prefix = format!("truffle-{app_id}-");
445 hostname.len() > prefix.len() && hostname.starts_with(&prefix)
446}
447
448impl super::super::NetworkProvider for TailscaleProvider {
449 async fn start(&mut self) -> Result<(), NetworkError> {
450 {
451 let current_state = *self.state.read().await;
452 if current_state != ProviderState::Stopped {
453 return Err(NetworkError::AlreadyRunning);
454 }
455 }
456 *self.state.write().await = ProviderState::Starting;
457
458 let token = Self::generate_session_token()?;
460 let token_hex = hex::encode(token);
461 *self.session_token.write().await = token;
462
463 let bridge = Bridge::bind(token).await?;
465 let bridge_port = bridge.local_port()?;
466 let bridge = Arc::new(bridge);
467
468 let (bridge_shutdown_tx, bridge_shutdown_rx) = tokio::sync::watch::channel(false);
470
471 {
473 let bridge_clone = bridge.clone();
474 tokio::spawn(async move {
475 bridge_clone.run(bridge_shutdown_rx).await;
476 });
477 }
478
479 *self.bridge.lock().await = Some(bridge.clone());
480 *self.bridge_shutdown_tx.lock().await = Some(bridge_shutdown_tx);
481
482 let sidecar_config = SidecarConfig {
484 binary_path: self.config.binary_path.clone(),
485 hostname: self.config.hostname.clone(),
486 state_dir: self.config.state_dir.clone(),
487 auth_key: self.config.auth_key.clone(),
488 bridge_port,
489 session_token_hex: token_hex,
490 ephemeral: self.config.ephemeral,
491 tags: self.config.tags.clone(),
492 idle_timeout_secs: self.config.idle_timeout_secs,
493 };
494
495 let (sidecar, sidecar_rx) = GoSidecar::spawn(sidecar_config.clone()).await?;
497
498 let (started_tx, started_rx) = oneshot::channel();
500
501 Self::spawn_event_processor(
503 sidecar_rx,
504 self.peers.clone(),
505 self.peer_event_tx.clone(),
506 self.health.clone(),
507 self.identity.clone(),
508 self.local_addr.clone(),
509 self.local_tailscale_id.clone(),
510 self.state.clone(),
511 Some(started_tx),
512 self.config.app_id.clone(),
513 );
514
515 sidecar.send_start(&sidecar_config).await?;
517
518 *self.sidecar.lock().await = Some(sidecar);
519
520 let auth_timeout = Duration::from_secs(300);
524 let result = tokio::time::timeout(auth_timeout, started_rx)
525 .await
526 .map_err(|_| {
527 NetworkError::StartFailed(
528 "timed out waiting for authentication (5 min). \
529 Subscribe to peer_events() to display auth URLs."
530 .into(),
531 )
532 })?
533 .map_err(|_| NetworkError::StartFailed("start signal channel dropped".into()))?;
534
535 match result {
536 Ok(()) => {
537 if let Some(ref sidecar) = *self.sidecar.lock().await {
539 let _ = sidecar.send_get_peers().await;
540 let _ = sidecar.send_watch_peers().await;
542 }
543 tracing::info!("tailscale provider started successfully");
544 Ok(())
545 }
546 Err(e) => {
547 *self.state.write().await = ProviderState::Stopped;
548 Err(e)
549 }
550 }
551 }
552
553 async fn stop(&self) -> Result<(), NetworkError> {
554 *self.state.write().await = ProviderState::Stopping;
555
556 if let Some(sidecar) = self.sidecar.lock().await.take() {
558 sidecar.shutdown().await;
559 }
560
561 if let Some(tx) = self.bridge_shutdown_tx.lock().await.take() {
563 let _ = tx.send(true);
564 }
565 *self.bridge.lock().await = None;
566
567 self.peers.write().await.clear();
569 *self.state.write().await = ProviderState::Stopped;
570 let mut h = self.health.write().await;
571 h.state = "stopped".to_string();
572 h.healthy = false;
573
574 tracing::info!("tailscale provider stopped");
575 Ok(())
576 }
577
578 fn local_identity(&self) -> NodeIdentity {
579 self.identity.read().unwrap().clone()
580 }
581
582 fn local_addr(&self) -> PeerAddr {
583 self.local_addr.read().unwrap().clone()
584 }
585
586 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent> {
587 self.peer_event_tx.subscribe()
588 }
589
590 async fn peers(&self) -> Vec<NetworkPeer> {
591 self.peers.read().await.values().cloned().collect()
592 }
593
594 async fn dial_tcp(&self, addr: &str, port: u16) -> Result<TcpStream, NetworkError> {
595 self.dial_tcp_opts(addr, port, DialOpts::default()).await
596 }
597
598 async fn dial_tcp_opts(
599 &self,
600 addr: &str,
601 port: u16,
602 opts: DialOpts,
603 ) -> Result<TcpStream, NetworkError> {
604 if *self.state.read().await != ProviderState::Running {
605 return Err(NetworkError::NotRunning);
606 }
607
608 let bridge = self
609 .bridge
610 .lock()
611 .await
612 .clone()
613 .ok_or(NetworkError::NotRunning)?;
614
615 let request_id = uuid::Uuid::new_v4().to_string();
617
618 let dial_rx = bridge.register_dial(request_id.clone()).await;
620
621 let event_rx = {
623 let sidecar_guard = self.sidecar.lock().await;
624 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
625
626 let event_rx = sidecar.subscribe();
627
628 sidecar
629 .send_dial(request_id.clone(), addr.to_string(), port, opts.tls)
630 .await?;
631
632 event_rx
633 };
634
635 Self::await_dial_result(&bridge, &request_id, dial_rx, event_rx, DIAL_TIMEOUT).await
640 }
641
642 async fn listen_tcp(&self, port: u16) -> Result<NetworkTcpListener, NetworkError> {
643 if *self.state.read().await != ProviderState::Running {
644 return Err(NetworkError::NotRunning);
645 }
646
647 let bridge = self
648 .bridge
649 .lock()
650 .await
651 .clone()
652 .ok_or(NetworkError::NotRunning)?;
653
654 let (tx, rx) = mpsc::channel::<IncomingConnection>(64);
656
657 let mut event_rx = {
659 let sidecar_guard = self.sidecar.lock().await;
660 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
661
662 let event_rx = sidecar.subscribe();
663 sidecar.send_listen(port, None).await?;
664 event_rx
665 };
666
667 let actual_port = tokio::time::timeout(Duration::from_secs(10), async {
671 loop {
672 match event_rx.recv().await {
673 Ok(SidecarInternalEvent::Listening { port: p }) if port == 0 || p == port => {
674 return Ok(p);
675 }
676 Ok(SidecarInternalEvent::Error { code, message }) => {
677 return Err(NetworkError::ListenFailed(format!("[{code}] {message}")));
678 }
679 Err(broadcast::error::RecvError::Closed) => {
680 return Err(NetworkError::SidecarError("event channel closed".into()));
681 }
682 _ => continue,
683 }
684 }
685 })
686 .await
687 .map_err(|_| NetworkError::ListenFailed("listen confirmation timed out".into()))??;
688
689 bridge.register_listener(actual_port, tx).await;
691
692 Ok(NetworkTcpListener {
693 port: actual_port,
694 incoming: rx,
695 })
696 }
697
698 async fn unlisten_tcp(&self, port: u16) -> Result<(), NetworkError> {
699 if *self.state.read().await != ProviderState::Running {
700 return Err(NetworkError::NotRunning);
701 }
702
703 let bridge = self
704 .bridge
705 .lock()
706 .await
707 .clone()
708 .ok_or(NetworkError::NotRunning)?;
709
710 bridge.remove_listener(port).await;
712
713 {
715 let sidecar_guard = self.sidecar.lock().await;
716 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
717 sidecar.send_unlisten(port).await?;
718 }
719
720 Ok(())
721 }
722
723 async fn ping(&self, addr: &str) -> Result<PingResult, NetworkError> {
724 if *self.state.read().await != ProviderState::Running {
725 return Err(NetworkError::NotRunning);
726 }
727
728 let target = addr.to_string();
729
730 let mut event_rx = {
732 let sidecar_guard = self.sidecar.lock().await;
733 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
734
735 let event_rx = sidecar.subscribe();
736 sidecar.send_ping(target.clone(), None).await?;
737 event_rx
738 };
739
740 let result = tokio::time::timeout(Duration::from_secs(15), async {
742 loop {
743 match event_rx.recv().await {
744 Ok(SidecarInternalEvent::PingResult(data)) if data.target == target => {
745 if !data.error.is_empty() {
746 return Err(NetworkError::PingFailed(data.error));
747 }
748 let connection = if data.direct {
749 "direct".to_string()
750 } else if !data.relay.is_empty() {
751 format!("relay:{}", data.relay)
752 } else {
753 "unknown".to_string()
754 };
755 return Ok(PingResult {
756 latency: Duration::from_secs_f64(data.latency_ms / 1000.0),
757 connection,
758 peer_addr: if data.peer_addr.is_empty() {
759 None
760 } else {
761 Some(data.peer_addr)
762 },
763 });
764 }
765 Err(broadcast::error::RecvError::Closed) => {
766 return Err(NetworkError::SidecarError("event channel closed".into()));
767 }
768 _ => continue,
769 }
770 }
771 })
772 .await
773 .map_err(|_| NetworkError::PingFailed("ping timed out".into()))?;
774
775 result
776 }
777
778 async fn bind_udp(&self, port: u16) -> Result<super::super::NetworkUdpSocket, NetworkError> {
779 if *self.state.read().await != ProviderState::Running {
780 return Err(NetworkError::NotRunning);
781 }
782
783 let mut event_rx = {
785 let sidecar_guard = self.sidecar.lock().await;
786 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
787
788 let event_rx = sidecar.subscribe();
789 sidecar.send_listen_packet(port).await?;
790 event_rx
791 };
792
793 let local_port = tokio::time::timeout(Duration::from_secs(10), async {
795 loop {
796 match event_rx.recv().await {
797 Ok(SidecarInternalEvent::ListeningPacket {
798 port: p,
799 local_port,
800 }) if p == port => {
801 return Ok(local_port);
802 }
803 Ok(SidecarInternalEvent::Error { code, message }) => {
804 return Err(NetworkError::ListenFailed(format!(
805 "UDP bind failed [{code}] {message}"
806 )));
807 }
808 Err(broadcast::error::RecvError::Closed) => {
809 return Err(NetworkError::SidecarError("event channel closed".into()));
810 }
811 _ => continue,
812 }
813 }
814 })
815 .await
816 .map_err(|_| {
817 NetworkError::ListenFailed("UDP listenPacket confirmation timed out".into())
818 })??;
819
820 let local_socket = tokio::net::UdpSocket::bind("127.0.0.1:0")
822 .await
823 .map_err(|e| NetworkError::Internal(format!("failed to bind local UDP socket: {e}")))?;
824
825 local_socket
826 .connect(format!("127.0.0.1:{local_port}"))
827 .await
828 .map_err(|e| {
829 NetworkError::Internal(format!("failed to connect local UDP socket to relay: {e}"))
830 })?;
831
832 let rust_local_addr = local_socket
833 .local_addr()
834 .map_err(|e| NetworkError::Internal(format!("failed to get local UDP addr: {e}")))?;
835
836 local_socket
841 .send(b"TRUFFLE_UDP_REGISTER")
842 .await
843 .map_err(|e| NetworkError::Internal(format!("failed to send UDP registration: {e}")))?;
844
845 tracing::info!(
846 tsnet_port = port,
847 relay_port = local_port,
848 rust_local_addr = %rust_local_addr,
849 "UDP socket bound via tsnet relay (registered)"
850 );
851
852 Ok(super::super::NetworkUdpSocket::new(local_socket, port))
853 }
854
855 async fn health(&self) -> HealthInfo {
856 self.health.read().await.clone()
857 }
858
859 async fn proxy_add(&self, config: ProxyAddParams) -> Result<ProxyAddResult, NetworkError> {
862 if *self.state.read().await != ProviderState::Running {
863 return Err(NetworkError::NotRunning);
864 }
865
866 let mut event_rx = {
868 let sidecar_guard = self.sidecar.lock().await;
869 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
870 let event_rx = sidecar.subscribe();
871 sidecar
872 .send_proxy_add(ProxyAddCommandData {
873 id: config.id.clone(),
874 name: config.name.clone(),
875 listen_port: config.listen_port,
876 target_host: config.target_host.clone(),
877 target_port: config.target_port,
878 target_scheme: config.target_scheme.clone(),
879 })
880 .await?;
881 event_rx
882 };
883
884 tokio::time::timeout(Duration::from_secs(10), async {
886 loop {
887 match event_rx.recv().await {
888 Ok(SidecarInternalEvent::ProxyAdded {
889 id,
890 listen_port,
891 url,
892 }) if id == config.id => {
893 return Ok(ProxyAddResult {
894 id,
895 listen_port,
896 url,
897 });
898 }
899 Ok(SidecarInternalEvent::ProxyError { id, code, message })
900 if id == config.id =>
901 {
902 return Err(NetworkError::ProxyError(format!("[{code}] {message}")));
903 }
904 Ok(SidecarInternalEvent::Error { code, message }) => {
905 return Err(NetworkError::ProxyError(format!("[{code}] {message}")));
906 }
907 Err(broadcast::error::RecvError::Closed) => {
908 return Err(NetworkError::SidecarError("event channel closed".into()));
909 }
910 Err(broadcast::error::RecvError::Lagged(_)) => {
911 return Err(NetworkError::SidecarError(
912 "event channel lagged: proxy confirmation may have been lost".into(),
913 ));
914 }
915 Ok(_) => continue,
916 }
917 }
918 })
919 .await
920 .map_err(|_| NetworkError::ProxyError("proxy add timed out".into()))?
921 }
922
923 async fn proxy_remove(&self, id: &str) -> Result<(), NetworkError> {
924 if *self.state.read().await != ProviderState::Running {
925 return Err(NetworkError::NotRunning);
926 }
927
928 let target_id = id.to_string();
929
930 let mut event_rx = {
932 let sidecar_guard = self.sidecar.lock().await;
933 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
934 let event_rx = sidecar.subscribe();
935 sidecar.send_proxy_remove(id).await?;
936 event_rx
937 };
938
939 tokio::time::timeout(Duration::from_secs(10), async {
941 loop {
942 match event_rx.recv().await {
943 Ok(SidecarInternalEvent::ProxyRemoved { id }) if id == target_id => {
944 return Ok(());
945 }
946 Ok(SidecarInternalEvent::ProxyError { id, code, message })
947 if id == target_id =>
948 {
949 return Err(NetworkError::ProxyError(format!("[{code}] {message}")));
950 }
951 Ok(SidecarInternalEvent::Error { code, message }) => {
952 return Err(NetworkError::ProxyError(format!("[{code}] {message}")));
953 }
954 Err(broadcast::error::RecvError::Closed) => {
955 return Err(NetworkError::SidecarError("event channel closed".into()));
956 }
957 Err(broadcast::error::RecvError::Lagged(_)) => {
958 return Err(NetworkError::SidecarError(
959 "event channel lagged: proxy confirmation may have been lost".into(),
960 ));
961 }
962 Ok(_) => continue,
963 }
964 }
965 })
966 .await
967 .map_err(|_| NetworkError::ProxyError("proxy remove timed out".into()))?
968 }
969
970 async fn proxy_list(&self) -> Result<Vec<ProxyListEntry>, NetworkError> {
971 if *self.state.read().await != ProviderState::Running {
972 return Err(NetworkError::NotRunning);
973 }
974
975 let mut event_rx = {
977 let sidecar_guard = self.sidecar.lock().await;
978 let sidecar = sidecar_guard.as_ref().ok_or(NetworkError::NotRunning)?;
979 let event_rx = sidecar.subscribe();
980 sidecar.send_proxy_list().await?;
981 event_rx
982 };
983
984 tokio::time::timeout(Duration::from_secs(10), async {
986 loop {
987 match event_rx.recv().await {
988 Ok(SidecarInternalEvent::ProxyList { proxies }) => {
989 return Ok(proxies
990 .into_iter()
991 .map(|p| ProxyListEntry {
992 id: p.id,
993 name: p.name,
994 listen_port: p.listen_port,
995 target_host: p.target_host,
996 target_port: p.target_port,
997 target_scheme: p.target_scheme,
998 url: p.url,
999 })
1000 .collect());
1001 }
1002 Ok(SidecarInternalEvent::Error { code, message }) => {
1003 return Err(NetworkError::ProxyError(format!("[{code}] {message}")));
1004 }
1005 Err(broadcast::error::RecvError::Closed) => {
1006 return Err(NetworkError::SidecarError("event channel closed".into()));
1007 }
1008 Err(broadcast::error::RecvError::Lagged(_)) => {
1009 return Err(NetworkError::SidecarError(
1010 "event channel lagged: proxy confirmation may have been lost".into(),
1011 ));
1012 }
1013 Ok(_) => continue,
1014 }
1015 }
1016 })
1017 .await
1018 .map_err(|_| NetworkError::ProxyError("proxy list timed out".into()))?
1019 }
1020}
1021
1022impl TailscaleProvider {
1023 pub async fn local_identity_async(&self) -> NodeIdentity {
1028 self.identity.read().unwrap().clone()
1029 }
1030
1031 pub async fn local_addr_async(&self) -> PeerAddr {
1036 self.local_addr.read().unwrap().clone()
1037 }
1038
1039 pub(super) async fn await_dial_result(
1049 bridge: &Bridge,
1050 request_id: &str,
1051 dial_rx: oneshot::Receiver<TcpStream>,
1052 mut event_rx: broadcast::Receiver<SidecarInternalEvent>,
1053 timeout: Duration,
1054 ) -> Result<TcpStream, NetworkError> {
1055 let fail_request_id = request_id.to_string();
1060 let (fail_tx, fail_rx) = oneshot::channel::<String>();
1061 let fail_watcher: JoinHandle<()> = tokio::spawn(async move {
1062 loop {
1063 match event_rx.recv().await {
1064 Ok(SidecarInternalEvent::DialFailed {
1065 request_id: rid,
1066 error,
1067 }) if rid == fail_request_id => {
1068 let _ = fail_tx.send(error);
1069 return;
1070 }
1071 Err(broadcast::error::RecvError::Closed) => {
1072 let _ = fail_tx.send("event channel closed".to_string());
1073 return;
1074 }
1075 _ => continue,
1076 }
1077 }
1078 });
1079
1080 let result = match tokio::time::timeout(timeout, async {
1081 tokio::select! {
1082 stream_result = dial_rx => {
1083 stream_result.map_err(|_| NetworkError::DialFailed("dial cancelled".into()))
1084 }
1085 fail_result = fail_rx => {
1086 let error = fail_result.unwrap_or_else(|_| "dial watcher dropped".to_string());
1087 Err(NetworkError::DialFailed(error))
1088 }
1089 }
1090 })
1091 .await
1092 {
1093 Ok(result) => result,
1094 Err(_) => Err(NetworkError::DialTimeout(timeout)),
1095 };
1096
1097 fail_watcher.abort();
1099
1100 if result.is_err() {
1102 bridge.remove_dial(request_id).await;
1103 }
1104
1105 result
1106 }
1107}