1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use virtual_net::IpAddr;

use crate::ApiOpts;

use super::AsyncCliCommand;

/// Connects to the Wasmer Edge distributed network.
#[derive(clap::Parser, Debug)]
pub struct CmdConnect {
    #[clap(flatten)]
    api: ApiOpts,

    /// Runs in promiscuous mode
    #[clap(long)]
    pub promiscuous: bool,
    /// Prints the network token rather than connecting
    #[clap(long)]
    pub print: bool,
    /// Skips bringing the interface up using the `ip` tool.
    #[clap(long)]
    pub leave_down: bool,
    /// Do not modify the postfix of the URL before connecting
    #[clap(long)]
    pub leave_postfix: bool,
    /// One or more static IP address to assign the interface
    #[clap(long)]
    pub ip: Vec<IpAddr>,
    /// Thr URL we will be connecting to
    #[clap(index = 1)]
    pub url: url::Url,
}

impl CmdConnect {
    #[cfg(all(target_os = "linux", feature = "tun-tap"))]
    async fn exec(mut self) -> Result<(), anyhow::Error> {
        use crate::net::TunTapSocket;
        use virtual_mio::Selector;
        use wasmer_deploy_schema::{AppId, NetworkIdEncodingMethod, WELL_KNOWN_VPN};

        // If the URL does not include the well known postfix then add it
        if !self.leave_postfix {
            self.url.set_path(WELL_KNOWN_VPN);
        }

        if self.print {
            println!("websocket-url: {}", self.url.as_str());
            return Ok(());
        }

        print!("Connecting...");
        let socket = TunTapSocket::create(
            Selector::new(),
            self.url.clone(),
            self.leave_down == false,
            self.ip,
        )
        .await
        .map_err(|err| {
            println!("failed");
            err
        })?;
        println!("\rConnected to {}    ", self.url.as_str());

        for cidr in socket.ips().iter() {
            println!("Your IP:  {}/{}", cidr.ip, cidr.prefix);
        }
        for route in socket.routes().iter() {
            println!(
                "Gateway: {}/{} -> {}",
                route.cidr.ip, route.cidr.prefix, route.via_router
            );
        }
        for cidr in socket.ips().iter() {
            if let Some((app_id, _)) =
                AppId::from_ip(&cidr.ip, NetworkIdEncodingMethod::PrivateProjection)
            {
                let ip = app_id.into_ip(
                    cidr.ip,
                    0x00_1001,
                    NetworkIdEncodingMethod::PrivateProjection,
                );
                println!("Instance: {}/{}", ip, cidr.prefix);
            }
        }
        println!("Press ctrl-c to terminate");
        socket.await?;

        Ok(())
    }

    #[cfg(not(all(target_os = "linux", feature = "tun-tap")))]
    async fn exec(self) -> Result<(), anyhow::Error> {
        Err(anyhow::anyhow!(
            "This CLI does not support the 'connect' command: only available on Linux (feature: tun-tap)"
        ))
    }
}

impl AsyncCliCommand for CmdConnect {
    fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> {
        Box::pin(self.exec())
    }
}