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