wasmer_cli/commands/run/capabilities/
net.rs1use 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#[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#[async_trait::async_trait]
167#[allow(unused_variables)]
168impl VirtualNetworking for AskingNetworking {
169 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 async fn unbridge(&self) -> Result<()> {
182 call!(self, unbridge);
183 }
184
185 async fn dhcp_acquire(&self) -> Result<Vec<IpAddr>> {
187 call!(self, dhcp_acquire);
188 }
189
190 async fn ip_add(&self, ip: IpAddr, prefix: u8) -> Result<()> {
192 call!(self, ip_add, ip, prefix);
193 }
194
195 async fn ip_remove(&self, ip: IpAddr) -> Result<()> {
197 call!(self, ip_remove, ip);
198 }
199
200 async fn ip_clear(&self) -> Result<()> {
202 call!(self, ip_clear);
203 }
204
205 async fn ip_list(&self) -> Result<Vec<IpCidr>> {
207 call!(self, ip_list);
208 }
209
210 async fn mac(&self) -> Result<[u8; 6]> {
212 call!(self, mac);
213 }
214
215 async fn gateway_set(&self, ip: IpAddr) -> Result<()> {
217 call!(self, gateway_set, ip);
218 }
219
220 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 async fn route_remove(&self, cidr: IpAddr) -> Result<()> {
240 call!(self, route_remove, cidr);
241 }
242
243 async fn route_clear(&self) -> Result<()> {
245 call!(self, route_clear);
246 }
247
248 async fn route_list(&self) -> Result<Vec<IpRoute>> {
250 call!(self, route_list);
251 }
252
253 async fn bind_raw(&self) -> Result<Box<dyn VirtualRawSocket + Sync>> {
256 call!(self, bind_raw);
257 }
258
259 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 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 async fn bind_icmp(&self, addr: IpAddr) -> Result<Box<dyn VirtualIcmpSocket + Sync>> {
287 call!(self, bind_icmp, addr);
288 }
289
290 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 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}