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