Skip to main content

cloudpub_client/
client.rs

1use anyhow::{anyhow, bail, Context, Result};
2use backoff::backoff::Backoff;
3use cloudpub_common::data::DataChannel;
4use cloudpub_common::fair_channel::{fair_channel, FairSender};
5use cloudpub_common::protocol::message::Message;
6use cloudpub_common::protocol::{
7    AgentInfo, ConnectState, Data, DataChannelAck, DataChannelData, DataChannelDataUdp,
8    DataChannelEof, ErrorInfo, ErrorKind, HeartBeat, Protocol,
9};
10use cloudpub_common::transport::{AddrMaybeCached, SocketOpts, Transport, WebsocketTransport};
11use cloudpub_common::utils::{
12    get_platform, proto_to_socket_addr, socket_addr_to_proto, udp_connect,
13};
14use cloudpub_common::VERSION;
15use dashmap::DashMap;
16use parking_lot::RwLock;
17use std::net::SocketAddr;
18use std::sync::Arc;
19use tokio::io::{AsyncReadExt, AsyncWriteExt};
20use tokio::net::{TcpStream, UdpSocket};
21use tokio::sync::mpsc;
22use tokio::time::{self, Duration, Instant};
23use tracing::{debug, error, info, trace, warn};
24
25use cloudpub_common::constants::{
26    run_control_chan_backoff, CONTROL_CHANNEL_SIZE, DATA_BUFFER_SIZE, DATA_CHANNEL_SIZE,
27    DEFAULT_CLIENT_RETRY_INTERVAL_SECS, UDP_BUFFER_SIZE, UDP_TIMEOUT,
28};
29use futures::future::FutureExt;
30
31use crate::config::{ClientConfig, ClientOpts};
32use crate::upgrade::handle_upgrade_available;
33use bytes::Bytes;
34use cloudpub_common::transport::ProtobufStream;
35
36#[cfg(feature = "plugins")]
37use crate::plugins::plugin_trait::PluginHandle;
38#[cfg(feature = "plugins")]
39use crate::plugins::registry::PluginRegistry;
40
41type Service = Arc<DataChannel>;
42
43type Services = Arc<DashMap<String, Service>>;
44
45/// A server-sent Error that the handshake ack path has already forwarded to
46/// result_tx. Typed so the outer retry loop can tell a real server refusal
47/// from a plain network failure: the server's own message must stay the last
48/// one the GUI sees (no synthetic error-network on top of it), and a fatal
49/// kind means retrying can never succeed (CLOUDPUB-1464).
50#[derive(Debug)]
51struct ServerRefusal {
52    kind: ErrorKind,
53}
54
55impl std::fmt::Display for ServerRefusal {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "Server refused the handshake: {:?}", self.kind)
58    }
59}
60
61impl std::error::Error for ServerRefusal {}
62
63/// What the outer retry loop does about a run_control_channel failure.
64struct RetryPlan {
65    /// Send the synthetic t!("error-network") ErrorInfo. Only when no real
66    /// server Error was forwarded for this failure - otherwise the synthetic
67    /// toast overwrites the server's meaningful message in the GUI
68    /// (CLOUDPUB-1464).
69    send_synthetic_network_error: bool,
70    /// Reconnect after backoff, or stop the client for good.
71    retry: bool,
72}
73
74/// The retry decision for one run_control_channel failure. Pure so the
75/// contract is unit-testable without sockets (CLOUDPUB-1464).
76fn plan_retry(err: &anyhow::Error) -> RetryPlan {
77    match err.downcast_ref::<ServerRefusal>() {
78        Some(refusal) => RetryPlan {
79            send_synthetic_network_error: false,
80            retry: !refusal.kind.is_fatal(),
81        },
82        None => RetryPlan {
83            send_synthetic_network_error: true,
84            retry: true,
85        },
86    }
87}
88
89// Holds the state of a client
90struct Client<T: Transport> {
91    config: Arc<RwLock<ClientConfig>>,
92    opts: ClientOpts,
93    services: Services,
94    transport: Arc<T>,
95    connected: bool,
96    #[cfg(feature = "plugins")]
97    plugin_processes: Arc<DashMap<String, PluginHandle>>,
98    data_channels: Arc<DashMap<u32, Arc<DataChannel>>>,
99}
100
101impl<T: 'static + Transport> Client<T> {
102    // Create a Client from `[client]` config block
103    async fn from(config: Arc<RwLock<ClientConfig>>, opts: ClientOpts) -> Result<Client<T>> {
104        let transport = Arc::new(
105            T::new(&config.clone().read().transport)
106                .with_context(|| "Failed to create the transport")?,
107        );
108        Ok(Client {
109            config,
110            opts,
111            services: Default::default(),
112            transport,
113            connected: false,
114            #[cfg(feature = "plugins")]
115            plugin_processes: Arc::new(DashMap::new()),
116            data_channels: Arc::new(DashMap::new()),
117        })
118    }
119
120    // The entrypoint of Client
121    async fn run(
122        &mut self,
123        mut command_rx: mpsc::Receiver<Message>,
124        result_tx: mpsc::Sender<Message>,
125    ) -> Result<()> {
126        let transport = self.transport.clone();
127
128        let mut retry_backoff = run_control_chan_backoff(DEFAULT_CLIENT_RETRY_INTERVAL_SECS);
129
130        let mut start = Instant::now();
131        result_tx
132            .send(Message::ConnectState(ConnectState::Connecting.into()))
133            .await
134            .context("Can't send Connecting event")?;
135        while let Err(err) = self
136            .run_control_channel(transport.clone(), &mut command_rx, &result_tx)
137            .boxed()
138            .await
139        {
140            if result_tx.is_closed() {
141                // The client is shutting down
142                break;
143            }
144
145            let plan = plan_retry(&err);
146
147            if self.connected {
148                if plan.send_synthetic_network_error {
149                    result_tx
150                        .send(Message::Error(ErrorInfo {
151                            kind: ErrorKind::HandshakeFailed.into(),
152                            message: crate::t!("error-network"),
153                            guid: String::new(),
154                        }))
155                        .await
156                        .context("Can't send Error event")?;
157                }
158                result_tx
159                    .send(Message::ConnectState(ConnectState::Disconnected.into()))
160                    .await
161                    .context("Can't send Disconnected event")?;
162                if plan.retry {
163                    result_tx
164                        .send(Message::ConnectState(ConnectState::Connecting.into()))
165                        .await
166                        .context("Can't send Connecting event")?;
167                }
168                self.connected = false;
169            }
170
171            self.services.clear();
172            #[cfg(feature = "plugins")]
173            self.plugin_processes.clear();
174            self.data_channels.clear();
175
176            if !plan.retry {
177                error!("Unrecoverable server refusal, stop retrying: {:#}", err);
178                break;
179            }
180
181            if start.elapsed() > Duration::from_secs(3) {
182                // The client runs for at least 3 secs and then disconnects
183                retry_backoff.reset();
184            }
185
186            if let Some(duration) = retry_backoff.next_backoff() {
187                warn!("{:#}. Retry in {:?}...", err, duration);
188                time::sleep(duration).await;
189            }
190
191            start = Instant::now();
192        }
193
194        self.services.clear();
195        #[cfg(feature = "plugins")]
196        self.plugin_processes.clear();
197        self.data_channels.clear();
198
199        Ok(())
200    }
201
202    async fn run_control_channel(
203        &mut self,
204        transport: Arc<T>,
205        command_rx: &mut mpsc::Receiver<Message>,
206        result_tx: &mpsc::Sender<Message>,
207    ) -> Result<()> {
208        let url = self.config.read().server.clone();
209        let port = url.port().unwrap_or(443);
210        let host = url.host_str().context("Failed to get host")?;
211        let mut host_and_port = format!("{}:{}", host, port);
212
213        let (mut conn, _remote_addr) = loop {
214            let mut remote_addr = AddrMaybeCached::new(&host_and_port);
215            remote_addr
216                .resolve()
217                .await
218                .context("Failed to resolve server address")?;
219
220            let mut conn = transport.connect(&remote_addr).await.context(format!(
221                "Failed to connect control channel to {}",
222                host_and_port
223            ))?;
224
225            self.connected = true;
226
227            T::hint(&conn, SocketOpts::for_control_channel());
228
229            let (email, password) = if let Some(ref cred) = self.opts.credentials {
230                (cred.0.clone(), cred.1.clone())
231            } else {
232                (String::new(), String::new())
233            };
234
235            let token = self
236                .config
237                .read()
238                .token
239                .clone()
240                .unwrap_or_default()
241                .to_string();
242
243            let hwid = self.config.read().get_hwid();
244
245            let agent_info = AgentInfo {
246                agent_id: self.config.read().agent_id.clone(),
247                token,
248                email,
249                password,
250                hostname: hostname::get()?.to_string_lossy().into_owned(),
251                version: VERSION.to_string(),
252                gui: self.opts.gui,
253                platform: get_platform(),
254                hwid,
255                server_host_and_port: host_and_port.clone(),
256                // The release line this agent self-upgrades in: the server
257                // offers upgrades out of download/<branch>/ (CLOUDPUB-1527)
258                branch: cloudpub_common::BRANCH.to_string(),
259                transient: self.opts.transient,
260                secondary: self.opts.secondary,
261                is_service: self.opts.is_service,
262            };
263
264            debug!("Sending hello: {:?}", agent_info);
265
266            let hello_send = Message::AgentHello(agent_info);
267
268            conn.send_message(&hello_send)
269                .await
270                .context("Failed to send hello message")?;
271
272            debug!("Reading ack");
273            match conn
274                .recv_message()
275                .await
276                .context("Failed to read ack message")?
277            {
278                Some(msg) => match msg {
279                    Message::AgentAck(args) => {
280                        if !args.token.is_empty() {
281                            let mut c = self.config.write();
282                            c.token = Some(args.token.as_str().into());
283                            c.save().context("Write config")?;
284                        }
285                        break (conn, remote_addr);
286                    }
287                    Message::Redirect(r) => {
288                        host_and_port = r.host_and_port.clone();
289                        debug!("Redirecting to {}", host_and_port);
290                        continue;
291                    }
292                    Message::Error(err) => {
293                        result_tx
294                            .send(Message::Error(err.clone()))
295                            .await
296                            .context("Can't send server error event")?;
297                        // The server's message is now the last one the GUI
298                        // saw; the typed refusal keeps the outer loop from
299                        // masking it and carries the kind for the retry
300                        // decision (CLOUDPUB-1464).
301                        return Err(ServerRefusal {
302                            kind: err.kind.try_into().unwrap_or(ErrorKind::Fatal),
303                        }
304                        .into());
305                    }
306                    v => bail!("Unexpected ack message: {:?}", v),
307                },
308                None => bail!("Connection closed while reading ack message"),
309            };
310        };
311
312        debug!("Control channel established");
313
314        result_tx
315            .send(Message::ConnectState(ConnectState::Connected.into()))
316            .await
317            .context("Can't send Connected event")?;
318
319        let (to_server_tx, mut to_server_rx) = fair_channel::<Message>(CONTROL_CHANNEL_SIZE);
320        // Used to break long running setup
321
322        let heartbeat_timeout = self.config.read().heartbeat_timeout;
323
324        loop {
325            tokio::select! {
326                cmd = to_server_rx.recv() => {
327                    if let Some(cmd) = cmd {
328                        conn.send_message(&cmd).await.context("Failed to send command")?;
329                    }
330                },
331                cmd = command_rx.recv() => {
332                    if let Some(cmd) = cmd {
333                        debug!("Received message: {:?}", cmd);
334                        match cmd {
335                            Message::PerformUpgrade(info) => {
336                                let config_clone = self.config.clone();
337                                if let Err(e) = handle_upgrade_available(
338                                    &info.version,
339                                    config_clone,
340                                    self.opts.gui,
341                                    command_rx,
342                                    result_tx,
343                                )
344                                .await
345                                {
346                                    result_tx.send(Message::Error(ErrorInfo {
347                                        kind: ErrorKind::Fatal.into(),
348                                        message: e.to_string(),
349                                        guid: String::new(),
350                                    }))
351                                    .await
352                                    .context("Can't send Error event")?;
353                                }
354                            }
355                            Message::Stop(_x) => {
356                                info!("Stopping the client");
357                                break;
358                            }
359                            Message::Break(break_msg) => {
360                                info!("Breaking operation for guid: {}", break_msg.guid);
361                                #[cfg(feature = "plugins")]
362                                if let Some((_, handle)) = self.plugin_processes.remove(&break_msg.guid) {
363                                    info!("Dropped plugin handle for guid: {}", break_msg.guid);
364                                    drop(handle);
365                                }
366                            }
367                            cmd => {
368                                conn.send_message(&cmd).await.context("Failed to send message")?;
369                            }
370                        };
371                    } else {
372                        debug!("No more commands, shutting down...");
373                        break;
374                    }
375                },
376                val = conn.recv_message() => {
377                    match val? {
378                        Some(val) => {
379                            match val {
380                                Message::EndpointAck(mut endpoint) => {
381                                    // The guid flows into plugin file paths
382                                    // ({guid}.conf/.pid/.lock, the onec publish
383                                    // dir), so a server-sent guid with path
384                                    // metacharacters must never get that far:
385                                    // reject the whole ack at receipt
386                                    // (CLOUDPUB-1313).
387                                    if !is_valid_guid(&endpoint.guid) {
388                                        warn!(
389                                            "Rejecting EndpointAck with unsafe guid: {:?}",
390                                            endpoint.guid
391                                        );
392                                        continue;
393                                    }
394                                    #[cfg(feature = "plugins")]
395                                    {
396                                        let to_server_tx = to_server_tx.clone();
397                                        let config = self.config.clone();
398                                        let opts = self.opts.clone();
399                                        if endpoint.error.is_empty() {
400                                            let protocol: Protocol = endpoint
401                                                .client
402                                                .as_ref()
403                                                .unwrap()
404                                                .local_proto
405                                                .try_into()
406                                                .unwrap_or(Protocol::Tcp);
407                                            if let Some(plugin) = PluginRegistry::new().get(protocol) {
408                                                // Duplicate EndpointStart for a publication this
409                                                // client already serves (the server re-sends them
410                                                // after EndpointStartAll and on repeated
411                                                // registrations): replacing the handle would kill
412                                                // the running subprocess mid-flight.
413                                                let already_running = self
414                                                    .plugin_processes
415                                                    .get(&endpoint.guid)
416                                                    .map(|h| h.same_config(&endpoint) && h.is_healthy())
417                                                    .unwrap_or(false);
418                                                if already_running {
419                                                    debug!(
420                                                        "Endpoint {} already running with the same config, \
421                                                         ignoring duplicate start",
422                                                        endpoint.guid
423                                                    );
424                                                } else {
425                                                    let handle = PluginHandle::spawn(
426                                                        plugin,
427                                                        endpoint.clone(),
428                                                        config,
429                                                        opts,
430                                                        to_server_tx,
431                                                    );
432                                                    self.plugin_processes.insert(endpoint.guid.clone(), handle);
433                                                }
434                                            } else {
435                                                endpoint.status = Some("online".into());
436                                                let _ = to_server_tx.send(Message::EndpointStatus(endpoint.clone())).await;
437                                            }
438                                        }
439                                    }
440                                    #[cfg(not(feature = "plugins"))]
441                                    if endpoint.error.is_empty() {
442                                        // A denied publication (the ack carries the error) must
443                                        // not be claimed online - the server has no listener for
444                                        // it, and the claim only forged a green status in the
445                                        // dashboard (CLOUDPUB-1466).
446                                        endpoint.status = Some("online".into());
447                                        let _ = to_server_tx.send(Message::EndpointStatus(endpoint.clone())).await;
448                                    }
449                                    result_tx
450                                        .send(Message::EndpointAck(endpoint))
451                                        .await
452                                        .context("Can't send EndpointAck event")?;
453                                }
454
455                                Message::CreateDataChannelWithId(create_msg) => {
456                                    let channel_id = create_msg.channel_id;
457                                    let endpoint = create_msg.endpoint.unwrap();
458
459                                    trace!("Creating data channel {} for endpoint {:?}", channel_id, endpoint.guid);
460
461                                    // Create channels for data flow
462                                    let (to_service_tx, to_service_rx) = mpsc::channel::<Data>(DATA_CHANNEL_SIZE);
463
464                                    // Register the data channel
465                                    let data_channel = Arc::new(DataChannel::new_client(channel_id, to_service_tx.clone()));
466                                    self.data_channels.insert(channel_id, data_channel.clone());
467
468                                    // Check if endpoint handled by plugin server
469                                    let client = endpoint.client.unwrap();
470                                    #[allow(unused_mut)]
471                                    let mut local_addr = format!("{}:{}", client.local_addr, client.local_port);
472                                    #[cfg(feature = "plugins")]
473                                    if let Some(handle) = self.plugin_processes.get(&endpoint.guid) {
474                                        if let Some(port) = handle.value().port() {
475                                            local_addr = format!("127.0.0.1:{}", port);
476                                        }
477                                    }
478
479                                    // Immediately start handling the data channel
480                                    let data_channels = self.data_channels.clone();
481                                    let protocol: Protocol = client.local_proto.try_into().unwrap();
482
483                                    let to_server_tx_cloned = to_server_tx.clone();
484                                    tokio::spawn(async move {
485                                        if let Err(err) = if protocol == Protocol::Udp {
486                                            handle_udp_data_channel(
487                                                data_channel,
488                                                local_addr,
489                                                to_server_tx_cloned.clone(),
490                                                to_service_rx
491                                            ).await
492                                        } else {
493                                            handle_tcp_data_channel(
494                                                data_channel,
495                                                local_addr,
496                                                to_server_tx_cloned.clone(),
497                                                to_service_rx
498                                            ).await
499                                        } {
500                                            error!("DataChannel {{ channel_id: {} }}: {:?}", channel_id, err);
501                                            to_server_tx_cloned
502                                                .send(Message::DataChannelEof(
503                                                        DataChannelEof {
504                                                            channel_id,
505                                                            error: err.to_string()
506                                                    })
507                                                ).await.ok();
508                                        }
509                                        if let Some((_, dc)) = data_channels.remove(&channel_id) { dc.close() }
510                                    });
511                                },
512
513                                Message::DataChannelData(data) => {
514                                    // Forward data to the appropriate data channel
515                                    let to_service_tx = self.data_channels.get(&data.channel_id).map(|ch| ch.data_tx.clone());
516                                    if let Some(tx) = to_service_tx {
517                                        if let Err(err) = tx.send(Data {
518                                            data: data.data.into(),
519                                            socket_addr: None
520                                        }).await {
521                                            self.data_channels.remove(&data.channel_id);
522                                            error!("Error send to data channel {}: {:?}", data.channel_id, err);
523                                        }
524                                    } else {
525                                        trace!("Data channel {} not found, dropping data", data.channel_id);
526                                    }
527                                },
528
529                                Message::DataChannelDataUdp(data) => {
530                                    // Forward UDP data to the appropriate data channel
531                                    let to_service_tx = self.data_channels.get(&data.channel_id).map(|ch| ch.data_tx.clone());
532                                    if let Some(tx) = to_service_tx {
533                                        let socket_addr = data.socket_addr.as_ref()
534                                            .map(proto_to_socket_addr)
535                                            .transpose()
536                                            .unwrap_or_else(|err| {
537                                                error!("Invalid socket address for UDP data channel {}: {:?}", data.channel_id, err);
538                                                None
539                                            });
540
541                                        if let Err(err) = tx.send(Data {
542                                            data: data.data.into(),
543                                            socket_addr,
544                                        }).await {
545                                            self.data_channels.remove(&data.channel_id);
546                                            error!("Error send to UDP data channel {}: {:?}", data.channel_id, err);
547                                        }
548                                    } else {
549                                        trace!("UDP Data channel {} not found, dropping data", data.channel_id);
550                                    }
551                                },
552
553                                Message::DataChannelEof(eof) => {
554                                    // Signal EOF by dropping the data channel
555                                    if let Some((_, dc)) = self.data_channels.remove(&eof.channel_id) { dc.close() }
556                                    if eof.error.is_empty() {
557                                        // Normal EOF without error
558                                        trace!("Data channel {} closed by server", eof.channel_id);
559                                    } else {
560                                        // EOF with error
561                                        trace!("Data channel {} closed by server with error: {}", eof.channel_id, eof.error);
562                                    }
563                                },
564
565                                Message::DataChannelAck(DataChannelAck { channel_id, consumed }) => {
566                                    if let Some(ch) = self.data_channels.get(&channel_id) {
567                                        ch.add_capacity(consumed);
568                                    }
569                                }
570
571                                Message::EndpointStopAck(ref ep) => {
572                                    self.services.remove(&ep.guid);
573                                    #[cfg(feature = "plugins")]
574                                    self.plugin_processes.remove(&ep.guid);
575                                    result_tx.send(val).await.context("Can't send result message")?;
576                                }
577
578                                Message::EndpointRemoveAck(ref ep) => {
579                                    self.services.remove(&ep.guid);
580                                    #[cfg(feature = "plugins")]
581                                    self.plugin_processes.remove(&ep.guid);
582                                    result_tx.send(val).await.context("Can't send result message")?;
583                                }
584
585                                Message::HeartBeat(_) => {
586                                    conn.send_message(&Message::HeartBeat(HeartBeat{})).await.context("Failed to send heartbeat")?;
587                                },
588
589                                Message::Error(ref err) => {
590                                    let kind: ErrorKind = err.kind.try_into().unwrap_or(ErrorKind::Fatal);
591                                    result_tx.send(val.clone()).await.context("Can't send result message")?;
592                                    if kind.is_fatal() {
593                                        error!("Fatal error received, stop client: {:?}", err);
594                                        break;
595                                    }
596                                }
597
598                                Message::Break(break_msg) => {
599                                    info!("Breaking operation for guid: {}", break_msg.guid);
600                                    #[cfg(feature = "plugins")]
601                                    self.plugin_processes.remove(&break_msg.guid);
602                                }
603
604                                Message::PerformUpgrade(info) => {
605                                    let config_clone = self.config.clone();
606                                    #[cfg(feature = "plugins")]
607                                    self.plugin_processes.clear();
608                                    self.services.clear();
609                                    self.data_channels.clear();
610
611                                    if let Err(e) = handle_upgrade_available(
612                                        &info.version,
613                                        config_clone,
614                                        self.opts.gui,
615                                        command_rx,
616                                        result_tx,
617                                    )
618                                    .await
619                                    {
620                                        conn.send_message(&Message::Error(ErrorInfo {
621                                            kind: ErrorKind::UpgradeFailed.into(),
622                                            message: e.to_string(),
623                                            guid: String::new(),
624                                        }))
625                                        .await
626                                        .context("Can't send Error event")?;
627                                    }
628                                }
629
630                                v => {
631                                    result_tx.send(v).await.context("Can't send result message")?;
632                                }
633                            }
634                        },
635                        None => {
636                            debug!("Connection closed by server");
637                            break;
638                        }
639                    }
640                },
641                _ = time::sleep(Duration::from_secs(heartbeat_timeout)), if heartbeat_timeout != 0 => {
642                    return Err(anyhow!("Heartbeat timed out"))
643                }
644            }
645        }
646
647        info!("Control channel shutdown");
648        result_tx
649            .send(Message::ConnectState(ConnectState::Disconnected.into()))
650            .await
651            .context("Can't send Disconnected event")?;
652        conn.close().await.ok();
653        time::sleep(Duration::from_millis(100)).await; // Give some time for the connection to close gracefully
654        Ok(())
655    }
656}
657
658pub async fn run_client(
659    config: Arc<RwLock<ClientConfig>>,
660    opts: ClientOpts,
661    command_rx: mpsc::Receiver<Message>,
662    result_tx: mpsc::Sender<Message>,
663) -> Result<()> {
664    let mut client = Client::<WebsocketTransport>::from(config, opts)
665        .await
666        .context("Failed to create Websocket client")?;
667    client.run(command_rx, result_tx).await
668}
669async fn handle_tcp_data_channel(
670    data_channel: Arc<DataChannel>,
671    local_addr: String,
672    to_server_tx: FairSender<Message>,
673    mut data_rx: mpsc::Receiver<Data>,
674) -> Result<()> {
675    trace!("Handling client {:?} to {}", data_channel, local_addr);
676
677    // Connect to local service immediately
678    let mut local_stream = TcpStream::connect(&local_addr)
679        .await
680        .with_context(|| format!("Failed to connect to local service at {}", local_addr))?;
681
682    // Set TCP_NODELAY for low latency
683    local_stream
684        .set_nodelay(true)
685        .context("Failed to set TCP_NODELAY")?;
686
687    let mut buf = [0u8; DATA_BUFFER_SIZE]; // Smaller buffer for low latency
688
689    loop {
690        tokio::select! {
691            res = local_stream.read(&mut buf) => {
692                match res {
693                    Ok(0) => {
694                        trace!("EOF received from local service for {:?}", data_channel);
695                        if let Err(err) = to_server_tx.send(Message::DataChannelEof(DataChannelEof {
696                            channel_id: data_channel.id,
697                            error: String::new()
698                        }))
699                        .await {
700                            trace!("Failed to send EOF to server for {:?}: {:#}", data_channel, err);
701                        }
702                        break;
703                    },
704                    Ok(n) => {
705                        //debug!("Read {} bytes from local service for {:?}", n, data_channel);
706                        if data_channel.wait_for_capacity(n as u32).await.is_err() {
707                            trace!("Data channel {} closed when waiting for capacity", data_channel.id);
708                            break;
709                        }
710                        if let Err(err) = to_server_tx.send(Message::DataChannelData(DataChannelData {
711                            channel_id: data_channel.id,
712                            data: buf[0..n].to_vec()
713                        }))
714                        .await {
715                            trace!("Failed to send data to server for {:?}: {:#}", data_channel, err);
716                            break;
717                        }
718                    },
719                    Err(e) => {
720                        return Err(e).context("Failed to read from local service");
721                    }
722                }
723            }
724
725            // Receive data from server via control channel and write to local service
726            data_result = data_rx.recv() => {
727                match data_result {
728                    Some(data) => {
729                        trace!("Received {} bytes from server for {:?}", data.data.len(), data_channel);
730                        local_stream.write_all(&data.data).await.context("Failed to write data to local service")?;
731                        to_server_tx.send(Message::DataChannelAck(
732                            DataChannelAck {
733                                channel_id: data_channel.id,
734                                consumed: data.data.len() as u32
735                            }
736                        )).await.with_context(|| "Failed to send TCP traffic ack to the server")?;
737                    },
738                    None => {
739                        trace!("EOF received from server for {:?}", data_channel);
740                        break;
741                    }
742                }
743            }
744
745            _ = data_channel.closed() => {
746                trace!("Data channel {} closed", data_channel.id);
747                break;
748            }
749        }
750    }
751    Ok(())
752}
753
754// UDP port map for managing forwarders per remote address
755type UdpPortMap = Arc<DashMap<SocketAddr, mpsc::Sender<Bytes>>>;
756
757async fn handle_udp_data_channel(
758    data_channel: Arc<DataChannel>,
759    local_addr: String,
760    to_server_tx: FairSender<Message>,
761    mut data_rx: mpsc::Receiver<Data>,
762) -> Result<()> {
763    trace!(
764        "Handling client UDP channel {:?} to {}",
765        data_channel,
766        local_addr
767    );
768
769    let port_map: UdpPortMap = Arc::new(DashMap::new());
770
771    loop {
772        let data_channel = data_channel.clone();
773        // Receive data from server via control channel
774        tokio::select! {
775            data = data_rx.recv() => {
776                match data {
777                    Some(data) => {
778                        let external_addr = data.socket_addr.unwrap();
779
780                        if !port_map.contains_key(&external_addr) {
781                            // This packet is from an address we haven't seen for a while,
782                            // which is not in the UdpPortMap.
783                            // So set up a mapping (and a forwarder) for it
784
785                            match udp_connect(&local_addr).await {
786                                Ok(s) => {
787                                    let (to_service_tx, to_service_rx) = mpsc::channel(DATA_CHANNEL_SIZE);
788                                    port_map.insert(external_addr, to_service_tx);
789                                    tokio::spawn(run_udp_forwarder(
790                                        s,
791                                        to_service_rx,
792                                        to_server_tx.clone(),
793                                        external_addr,
794                                        data_channel,
795                                        port_map.clone(),
796                                    ));
797                                }
798                                Err(e) => {
799                                    error!(
800                                        "Failed to create UDP forwarder for {}: {:#}",
801                                        external_addr, e
802                                    );
803                                }
804                            }
805                        }
806
807                        // Now there should be a udp forwarder that can receive the packet
808                        if let Some(tx) = port_map.get(&external_addr) {
809                            let _ = tx.send(data.data).await;
810                        }
811                    }
812                    None => {
813                        trace!("EOF received from server for UDP {:?}", data_channel);
814                        break;
815                    }
816                }
817            }
818            _ = data_channel.closed() => {
819                trace!("Data channel {} closed", data_channel.id);
820                break;
821            }
822        }
823    }
824    Ok(())
825}
826
827// Run a UdpSocket for the visitor `from`
828async fn run_udp_forwarder(
829    s: UdpSocket,
830    mut to_service_rx: mpsc::Receiver<Bytes>,
831    to_server_tx: FairSender<Message>,
832    from: SocketAddr,
833    data_channel: Arc<DataChannel>,
834    port_map: UdpPortMap,
835) -> Result<()> {
836    trace!("UDP forwarder created for {} on {:?}", from, data_channel);
837    let mut buf = vec![0u8; UDP_BUFFER_SIZE];
838
839    loop {
840        tokio::select! {
841            // Receive from the server
842            data = to_service_rx.recv() => {
843                if let Some(data) = data {
844                    s.send(&data).await.with_context(|| "Failed to send UDP traffic to the service")?;
845                    to_server_tx.send(Message::DataChannelAck(
846                        DataChannelAck {
847                            channel_id: data_channel.id,
848                            consumed: data.len() as u32
849                        }
850                    )).await.with_context(|| "Failed to send UDP traffic ack to the server")?;
851                } else {
852                    break;
853                }
854            },
855
856            // Receive from the service
857            val = s.recv(&mut buf) => {
858                let len = match val {
859                    Ok(v) => v,
860                    Err(_) => break
861                };
862
863                if data_channel.wait_for_capacity(len as u32).await.is_err() {
864                    break;
865                }
866
867                to_server_tx.send(Message::DataChannelDataUdp(
868                    DataChannelDataUdp {
869                    channel_id: data_channel.id,
870                    data: buf[..len].to_vec(),
871                    socket_addr: Some(socket_addr_to_proto(&from)),
872                })).await.with_context(|| "Failed to send UDP traffic to the server")?;
873            },
874
875            // No traffic for the duration of UDP_TIMEOUT, clean up the state
876            _ = time::sleep(Duration::from_secs(UDP_TIMEOUT)) => {
877                break;
878            }
879
880            _ = data_channel.closed() => {
881                trace!("Data channel {} closed", data_channel.id);
882                break;
883            }
884        }
885    }
886
887    port_map.remove(&from);
888
889    debug!("UDP forwarder dropped for {} on {:?}", from, data_channel);
890    Ok(())
891}
892
893/// A guid arriving from the server ends up in filesystem paths (the httpd
894/// plugin's {guid}.conf/.pid/.lock, the onec publish dir), so it is validated
895/// once, at receipt: strictly ASCII alphanumeric plus `-`/`_`, which covers
896/// every UUID the server legitimately sends and rejects `.`, `/` and `\`
897/// outright (CLOUDPUB-1313).
898fn is_valid_guid(guid: &str) -> bool {
899    !guid.is_empty()
900        && guid
901            .bytes()
902            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
903}
904
905#[cfg(test)]
906mod tests {
907    use super::*;
908
909    // HYPOTHESIS: endpoint.guid arrives from the server in EndpointAck and is
910    // pushed straight into filesystem paths ({guid}.conf/.pid/.lock in the
911    // httpd plugin, publish_dir.join(guid) in onec), so a malicious server
912    // can traverse out of the working dir with a guid like "../evil"
913    // (CLOUDPUB-1313). The receipt handler must reject such a guid before any
914    // plugin spawns and writes files.
915    // RESULT: red first - is_valid_guid did not exist; green once the
916    // EndpointAck arm gates on it and skips processing for unsafe guids.
917    // STATUS: FIXED - see CLOUDPUB-1313
918    #[test]
919    fn test_endpoint_ack_rejects_path_traversal_guid() {
920        // Everything the server legitimately sends: UUIDs and simple ids.
921        assert!(is_valid_guid("550e8400-e29b-41d4-a716-446655440000"));
922        assert!(is_valid_guid("abc_DEF-123"));
923
924        // Path metacharacters in every convention must be rejected outright.
925        assert!(!is_valid_guid("../evil"));
926        assert!(!is_valid_guid("a/b"));
927        assert!(!is_valid_guid("a\\b"));
928        assert!(!is_valid_guid(".."));
929        assert!(!is_valid_guid("."));
930        assert!(!is_valid_guid(""));
931        assert!(!is_valid_guid("x/../../etc/cron.d/job"));
932        assert!(!is_valid_guid("null\0byte"));
933    }
934
935    // HYPOTHESIS: two defects around the outer retry loop in run()
936    // (CLOUDPUB-1464). (1) After run_control_channel fails, the loop always
937    // sends a synthetic HandshakeFailed/t!("error-network") ErrorInfo -
938    // self.connected is already true right after TCP connect, before the
939    // handshake - so the server's meaningful refusal, which the ack path had
940    // just forwarded, is overwritten in the GUI by "Network error". (2) The
941    // loop never inspects the error kind, so a fatal refusal (guid owned by
942    // another account, sent as ErrorKind::Fatal by the fixed server) is
943    // retried forever and the GUI hangs in "Connecting".
944    // SOLUTION: the ack path returns the typed ServerRefusal after forwarding
945    // the server Error, and the loop acts on plan_retry(): a forwarded
946    // refusal suppresses the synthetic error, a fatal one ends the loop with
947    // ConnectState::Disconnected as the final state.
948    // RESULT: red first - plan_retry() unconditionally returned the status
949    // quo (synthetic error + retry); green once it downcasts ServerRefusal
950    // and honors ErrorKind::is_fatal().
951    // STATUS: FIXED - see CLOUDPUB-1464
952    #[test]
953    fn fatal_server_refusal_stops_retrying_without_masking() {
954        let err: anyhow::Error = ServerRefusal {
955            kind: ErrorKind::Fatal,
956        }
957        .into();
958        let plan = plan_retry(&err);
959        assert!(
960            !plan.retry,
961            "a fatal server refusal can never succeed by retrying"
962        );
963        assert!(
964            !plan.send_synthetic_network_error,
965            "the server's own message must stay the last one the GUI sees"
966        );
967    }
968
969    #[test]
970    fn non_fatal_server_refusal_still_retries_without_masking() {
971        // HandshakeFailed stays non-fatal by design: the server labels
972        // transient causes (DB errors) with it, so the client keeps retrying.
973        let err: anyhow::Error = ServerRefusal {
974            kind: ErrorKind::HandshakeFailed,
975        }
976        .into();
977        let plan = plan_retry(&err);
978        assert!(plan.retry, "transient refusals must keep retrying");
979        assert!(
980            !plan.send_synthetic_network_error,
981            "the forwarded server message must not be overwritten by the \
982             synthetic network error"
983        );
984    }
985
986    #[test]
987    fn lease_busy_refusal_still_retries() {
988        // LeaseBusy is temporary by construction (CLOUDPUB-1339) - the fix
989        // for CLOUDPUB-1464 must not turn it into a stop.
990        let err: anyhow::Error = ServerRefusal {
991            kind: ErrorKind::LeaseBusy,
992        }
993        .into();
994        assert!(plan_retry(&err).retry);
995    }
996
997    #[test]
998    fn plain_network_failure_keeps_synthetic_error_and_retry() {
999        // No server Error was forwarded: today's behavior stays - report the
1000        // localized network error and reconnect.
1001        let err = anyhow::anyhow!("Failed to connect control channel to example.com:443");
1002        let plan = plan_retry(&err);
1003        assert!(plan.retry);
1004        assert!(plan.send_synthetic_network_error);
1005    }
1006
1007    #[test]
1008    fn server_refusal_is_detected_through_context_wrapping() {
1009        // anyhow context layers must not hide the refusal from the loop.
1010        let err = anyhow::Error::new(ServerRefusal {
1011            kind: ErrorKind::Fatal,
1012        })
1013        .context("run_control_channel failed");
1014        assert!(!plan_retry(&err).retry);
1015    }
1016}