wasmer_cli/commands/run/capabilities/
net.rs

1use std::{
2    fmt::Display,
3    net::{IpAddr, SocketAddr},
4    path::PathBuf,
5    str::FromStr,
6    sync::OnceLock,
7    time::Duration,
8};
9
10use super::{super::PackageSource, PkgCapabilityCache};
11use anyhow::Context;
12use colored::Colorize;
13use dialoguer::theme::ColorfulTheme;
14use virtual_net::{
15    DynVirtualNetworking, IpCidr, IpRoute, NetworkError, Result, StreamSecurity,
16    UnsupportedVirtualNetworking, VirtualIcmpSocket, VirtualNetworking, VirtualRawSocket,
17    VirtualTcpListener, VirtualTcpSocket, VirtualUdpSocket,
18};
19
20/// A custom implementation of the [`virtual_net::VirtualNetwork`] that asks users if they want to
21/// use networking features at runtime.
22#[derive(Debug, Clone)]
23pub(crate) struct AskingNetworking {
24    pkg_cache_path: PathBuf,
25    enable: OnceLock<Result<bool>>,
26    capable: DynVirtualNetworking,
27    unsupported: DynVirtualNetworking,
28}
29
30macro_rules! call {
31    ($self: expr, $fn_name: ident, $( $arg: expr ),* ) => {
32
33        let enable_networking = $self.enable.get_or_init(|| $self.ask_user(stringify!($fn_name)))
34            .map_err(|e| {
35                tracing::error!("{e}");
36                NetworkError::UnknownError
37            })?;
38
39        if enable_networking {
40            return $self.capable.$fn_name( $( $arg ),* ).await;
41        } else {
42            return $self.unsupported.$fn_name( $( $arg ),* ).await;
43        }
44    };
45
46    ($self: expr, $fn_name: ident) => {
47
48        let enable_networking = $self.enable.get_or_init(|| $self.ask_user(stringify!($fn_name))).map_err(|e| {
49                tracing::error!("{e}");
50                NetworkError::UnknownError
51            })?;
52
53        if enable_networking {
54            return $self.capable.$fn_name().await;
55        } else {
56            return $self.unsupported.$fn_name().await;
57        }
58    };
59}
60
61#[derive(Debug, Clone)]
62enum UserSelection {
63    Yes,
64    No,
65    Always,
66}
67
68impl Display for UserSelection {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            UserSelection::Yes => write!(f, "yes"),
72            UserSelection::No => write!(f, "no"),
73            UserSelection::Always => write!(f, "always"),
74        }
75    }
76}
77
78impl FromStr for UserSelection {
79    type Err = anyhow::Error;
80
81    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
82        let s = s.trim();
83        if s.is_empty() {
84            anyhow::bail!("No input!")
85        }
86
87        if let Some(c) = s.trim().chars().next().map(|c| c.to_ascii_lowercase()) {
88            Ok(match c {
89                'n' => UserSelection::No,
90                'y' => UserSelection::Yes,
91                'a' => UserSelection::Always,
92                _ => anyhow::bail!("{s} could not be resolved as a selection"),
93            })
94        } else {
95            anyhow::bail!("{s} could not be resolved as a selection")
96        }
97    }
98}
99
100impl AskingNetworking {
101    pub(crate) fn new(pkg_cache_path: PathBuf, capable_networking: DynVirtualNetworking) -> Self {
102        let enable_networking = OnceLock::new();
103
104        Self {
105            enable: enable_networking,
106            capable: capable_networking,
107            unsupported: std::sync::Arc::new(UnsupportedVirtualNetworking::default()),
108            pkg_cache_path,
109        }
110    }
111
112    fn ask_user(&self, fn_name: &str) -> Result<bool> {
113        let theme = ColorfulTheme::default();
114
115        println!("The current package is requesting networking access.");
116        println!("Run the package with `--net` flag to bypass the prompt.");
117        match dialoguer::Input::with_theme(&theme)
118            .with_prompt(format!(
119                "Would you like to allow networking for this package? {}{}",
120                "".bold(),
121                "[options: yes/no/always]".dimmed()
122            ))
123            .default(UserSelection::Always)
124            .interact()
125            .map_err(|_| NetworkError::UnknownError)?
126        {
127            UserSelection::No => Ok(false),
128            UserSelection::Yes => Ok(true),
129            UserSelection::Always => {
130                self.save_in_cache();
131                Ok(true)
132            }
133        }
134    }
135
136    fn save_in_cache(&self) -> Result<()> {
137        let capability = PkgCapabilityCache {
138            enable_networking: true,
139        };
140
141        if let Some(parent) = self.pkg_cache_path.parent() {
142            std::fs::create_dir_all(parent)
143                .context("could not create cache dir")
144                .map_err(|e| {
145                    tracing::error!("e");
146                    NetworkError::UnknownError
147                })?;
148        }
149
150        let data = serde_json::to_string_pretty(&capability).map_err(|e| {
151            tracing::error!("e");
152            NetworkError::UnknownError
153        })?;
154
155        std::fs::write(&self.pkg_cache_path, data).map_err(|e| {
156            tracing::error!("e");
157            NetworkError::UnknownError
158        })?;
159        tracing::trace!(path=%self.pkg_cache_path.display(), "persisted app template cache");
160
161        Ok(())
162    }
163}
164
165/// An implementation of virtual networking
166#[async_trait::async_trait]
167#[allow(unused_variables)]
168impl VirtualNetworking for AskingNetworking {
169    /// Bridges this local network with a remote network, which is required in
170    /// order to make lower level networking calls (such as UDP/TCP)
171    async fn bridge(
172        &self,
173        network: &str,
174        access_token: &str,
175        security: StreamSecurity,
176    ) -> Result<()> {
177        call!(self, bridge, network, access_token, security);
178    }
179
180    /// Disconnects from the remote network essentially unbridging it
181    async fn unbridge(&self) -> Result<()> {
182        call!(self, unbridge);
183    }
184
185    /// Acquires an IP address on the network and configures the routing tables
186    async fn dhcp_acquire(&self) -> Result<Vec<IpAddr>> {
187        call!(self, dhcp_acquire);
188    }
189
190    /// Adds a static IP address to the interface with a netmask prefix
191    async fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> {
192        call!(self, ip_add, ip, prefix);
193    }
194
195    /// Removes a static (or dynamic) IP address from the interface
196    async fn ip_remove(&self, ip: IpAddr) -> Result<()> {
197        call!(self, ip_remove, ip);
198    }
199
200    /// Clears all the assigned IP addresses for this interface
201    async fn ip_clear(&self) -> Result<()> {
202        call!(self, ip_clear);
203    }
204
205    /// Lists all the IP addresses currently assigned to this interface
206    async fn ip_list(&self) -> Result<Vec<IpCidr>> {
207        call!(self, ip_list);
208    }
209
210    /// Returns the hardware MAC address for this interface
211    async fn mac(&self) -> Result<[u8; 6]> {
212        call!(self, mac);
213    }
214
215    /// Adds a default gateway to the routing table
216    async fn gateway_set(&self, ip: IpAddr) -> Result<()> {
217        call!(self, gateway_set, ip);
218    }
219
220    /// Adds a specific route to the routing table
221    async fn route_add(
222        &self,
223        cidr: IpCidr,
224        via_router: IpAddr,
225        preferred_until: Option<Duration>,
226        expires_at: Option<Duration>,
227    ) -> Result<()> {
228        call!(
229            self,
230            route_add,
231            cidr,
232            via_router,
233            preferred_until,
234            expires_at
235        );
236    }
237
238    /// Removes a routing rule from the routing table
239    async fn route_remove(&self, cidr: IpAddr) -> Result<()> {
240        call!(self, route_remove, cidr);
241    }
242
243    /// Clears the routing table for this interface
244    async fn route_clear(&self) -> Result<()> {
245        call!(self, route_clear);
246    }
247
248    /// Lists all the routes defined in the routing table for this interface
249    async fn route_list(&self) -> Result<Vec<IpRoute>> {
250        call!(self, route_list);
251    }
252
253    /// Creates a low level socket that can read and write Ethernet packets
254    /// directly to the interface
255    async fn bind_raw(&self) -> Result<Box<dyn VirtualRawSocket + Sync>> {
256        call!(self, bind_raw);
257    }
258
259    /// Lists for TCP connections on a specific IP and Port combination
260    /// Multiple servers (processes or threads) can bind to the same port if they each set
261    /// the reuse-port and-or reuse-addr flags
262    async fn listen_tcp(
263        &self,
264        addr: SocketAddr,
265        only_v6: bool,
266        reuse_port: bool,
267        reuse_addr: bool,
268    ) -> Result<Box<dyn VirtualTcpListener + Sync>> {
269        call!(self, listen_tcp, addr, only_v6, reuse_port, reuse_addr);
270    }
271
272    /// Opens a UDP socket that listens on a specific IP and Port combination
273    /// Multiple servers (processes or threads) can bind to the same port if they each set
274    /// the reuse-port and-or reuse-addr flags
275    async fn bind_udp(
276        &self,
277        addr: SocketAddr,
278        reuse_port: bool,
279        reuse_addr: bool,
280    ) -> Result<Box<dyn VirtualUdpSocket + Sync>> {
281        call!(self, bind_udp, addr, reuse_port, reuse_addr);
282    }
283
284    /// Creates a socket that can be used to send and receive ICMP packets
285    /// from a paritcular IP address
286    async fn bind_icmp(&self, addr: IpAddr) -> Result<Box<dyn VirtualIcmpSocket + Sync>> {
287        call!(self, bind_icmp, addr);
288    }
289
290    /// Opens a TCP connection to a particular destination IP address and port
291    async fn connect_tcp(
292        &self,
293        addr: SocketAddr,
294        peer: SocketAddr,
295    ) -> Result<Box<dyn VirtualTcpSocket + Sync>> {
296        call!(self, connect_tcp, addr, peer);
297    }
298
299    /// Performs DNS resolution for a specific hostname
300    async fn resolve(
301        &self,
302        host: &str,
303        port: Option<u16>,
304        dns_server: Option<IpAddr>,
305    ) -> Result<Vec<IpAddr>> {
306        call!(self, resolve, host, port, dns_server);
307    }
308}