1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Conversion between Rust and C configurations.
16 use crate::libslirp_sys::{self, SLIRP_MAX_DNS_SERVERS};
17 use log::warn;
18 use std::ffi::CString;
19 use std::io;
20 use std::net::SocketAddr;
21 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
22 use std::path::PathBuf;
23 use tokio;
24
25 /// The maximum number of DNS servers supported by libslirp.
26 const MAX_DNS_SERVERS: usize = SLIRP_MAX_DNS_SERVERS as usize;
27
28 /// Configuration options for the Slirp network stack.
29 pub struct SlirpConfig {
30 /// Slirp version.
31 pub version: u32,
32 /// Whether to run in restricted mode.
33 pub restricted: i32,
34 /// Whether IPv4 is enabled.
35 pub in_enabled: bool,
36 /// The virtual network address for IPv4.
37 pub vnetwork: Ipv4Addr,
38 /// The virtual network mask for IPv4.
39 pub vnetmask: Ipv4Addr,
40 /// The virtual host address for IPv4.
41 pub vhost: Ipv4Addr,
42 /// Whether IPv6 is enabled.
43 pub in6_enabled: bool,
44 /// The virtual prefix address for IPv6.
45 pub vprefix_addr6: Ipv6Addr,
46 /// The length of the virtual prefix for IPv6.
47 pub vprefix_len: u8,
48 /// The virtual host address for IPv6.
49 pub vhost6: Ipv6Addr,
50 /// The virtual hostname.
51 pub vhostname: Option<String>,
52 /// The TFTP server name.
53 pub tftp_server_name: Option<String>,
54 /// The path to the TFTP root directory.
55 pub tftp_path: Option<PathBuf>,
56 /// The bootfile name for DHCP.
57 pub bootfile: Option<String>,
58 /// The starting IP address for the DHCP server.
59 pub vdhcp_start: Ipv4Addr,
60 /// The primary DNS server address for IPv4.
61 pub vnameserver: Ipv4Addr,
62 /// The primary DNS server address for IPv6.
63 pub vnameserver6: Ipv6Addr,
64 /// A list of DNS search domains.
65 pub vdnssearch: Vec<String>,
66 /// The virtual domain name.
67 pub vdomainname: Option<String>,
68 /// The interface MTU (Maximum Transmission Unit).
69 pub if_mtu: usize,
70 /// The interface MRU (Maximum Receive Unit).
71 pub if_mru: usize,
72 /// Whether to disable the host loopback interface.
73 pub disable_host_loopback: bool,
74 /// Whether to enable emulation features.
75 pub enable_emu: bool,
76 /// The outbound IPv4 address to bind to (optional).
77 pub outbound_addr: Option<SocketAddrV4>,
78 /// The outbound IPv6 address to bind to (optional).
79 pub outbound_addr6: Option<SocketAddrV6>,
80 /// Whether to disable the built-in DNS server.
81 pub disable_dns: bool,
82 /// Whether to disable the built-in DHCP server.
83 pub disable_dhcp: bool,
84 /// The manufacturer ID.
85 pub mfr_id: u32,
86 /// The out-of-band Ethernet address.
87 pub oob_eth_addr: [u8; 6usize],
88 /// Whether the HTTP proxy is enabled.
89 pub http_proxy_on: bool,
90 /// A list of host DNS servers to use.
91 pub host_dns: Vec<SocketAddr>,
92 }
93
94 impl Default for SlirpConfig {
95 /// Creates a new `SlirpConfig` with default values.
96 ///
97 /// The default configuration has IPv4 and IPv6 enabled on a private network,
98 /// with DHCP starting at `10.0.2.16` and a DNS server at `10.0.2.3`.
default() -> Self99 fn default() -> Self {
100 SlirpConfig {
101 version: 5,
102 // No restrictions by default
103 restricted: 0,
104 in_enabled: true,
105 // Private network address
106 vnetwork: Ipv4Addr::new(10, 0, 2, 0),
107 vnetmask: Ipv4Addr::new(255, 255, 255, 0),
108 // Default host address
109 vhost: Ipv4Addr::new(10, 0, 2, 2),
110 // IPv6 enabled by default
111 in6_enabled: true,
112 vprefix_addr6: "fec0::".parse().unwrap(),
113 vprefix_len: 64,
114 vhost6: "fec0::2".parse().unwrap(),
115 vhostname: None, // Some("slirp".to_string()),
116 tftp_server_name: None,
117 tftp_path: None,
118 bootfile: None,
119 // DHCP starting address
120 vdhcp_start: Ipv4Addr::new(10, 0, 2, 16),
121 // Public DNS server
122 vnameserver: Ipv4Addr::new(10, 0, 2, 3),
123 vnameserver6: "fec0::3".parse().unwrap(),
124 vdnssearch: Vec::new(),
125 vdomainname: None,
126 // Ethernet MTU
127 if_mtu: 0,
128 // Ethernet MRU
129 if_mru: 0,
130 disable_host_loopback: false,
131 enable_emu: false,
132 outbound_addr: None,
133 outbound_addr6: None,
134 disable_dns: false,
135 disable_dhcp: false,
136 mfr_id: 0,
137 oob_eth_addr: [0; 6usize],
138 http_proxy_on: false,
139 host_dns: Vec::new(),
140 }
141 }
142 }
143
144 /// Struct to hold a "C" `SlirpConfig` and the Rust storage that is
145 /// referenced by `SlirpConfig`.
146 #[allow(dead_code)]
147 pub struct SlirpConfigs {
148 /// The "C" representation of the Slirp configuration.
149 pub c_slirp_config: libslirp_sys::SlirpConfig,
150
151 // fields that hold the managed storage for "C" struct.
152 c_bootfile: Option<CString>,
153 c_tftp_server_name: Option<CString>,
154 c_vdomainname: Option<CString>,
155 c_vhostname: Option<CString>,
156 c_tftp_path: Option<CString>,
157 c_host_dns: [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS],
158 // TODO: add other fields
159 }
160
161 /// Asynchronously looks up the IP addresses for a given hostname or comma-separated list of hostnames.
162 ///
163 /// Each hostname in the input string is resolved using `tokio::net::lookup_host`.
164 /// The port in the resolved `SocketAddr` will be 0.
165 ///
166 /// # Arguments
167 ///
168 /// * `host_dns` - A string containing a single hostname or a comma-separated list of hostnames.
169 ///
170 /// # Returns
171 ///
172 /// A `Result` containing a `Vec` of `SocketAddr` on success, or an `io::Error` on failure.
lookup_host_dns(host_dns: &str) -> io::Result<Vec<SocketAddr>>173 pub async fn lookup_host_dns(host_dns: &str) -> io::Result<Vec<SocketAddr>> {
174 let mut set = tokio::task::JoinSet::new();
175 if host_dns.is_empty() {
176 return Ok(Vec::new());
177 }
178
179 for addr in host_dns.split(',') {
180 set.spawn(tokio::net::lookup_host(format!("{addr}:0")));
181 }
182
183 let mut addrs = Vec::new();
184 while let Some(result) = set.join_next().await {
185 addrs.push(result??.next().ok_or(io::Error::from(io::ErrorKind::NotFound))?);
186 }
187 Ok(addrs)
188 }
189
190 /// Converts a slice of `SocketAddr` into an array of `libslirp_sys::sockaddr_storage`.
191 ///
192 /// If the input slice contains more than `MAX_DNS_SERVERS` addresses, a warning is logged,
193 /// and only the first `MAX_DNS_SERVERS` addresses are converted. The remaining entries
194 /// in the output array will be default-initialized.
195 ///
196 /// # Arguments
197 ///
198 /// * `dns` - A slice of `SocketAddr` representing DNS server addresses.
199 ///
200 /// # Returns
201 ///
202 /// An array of `libslirp_sys::sockaddr_storage` containing the converted addresses.
to_socketaddr_storage(dns: &[SocketAddr]) -> [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS]203 fn to_socketaddr_storage(dns: &[SocketAddr]) -> [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS] {
204 let mut result = [libslirp_sys::sockaddr_storage::default(); MAX_DNS_SERVERS];
205 if dns.len() > MAX_DNS_SERVERS {
206 warn!("Too many DNS servers, only keeping the first {} ones", MAX_DNS_SERVERS);
207 }
208 for i in 0..usize::min(dns.len(), MAX_DNS_SERVERS) {
209 result[i] = dns[i].into();
210 }
211 result
212 }
213
214 impl SlirpConfigs {
215 /// Creates a new `SlirpConfigs` instance from a Rust `SlirpConfig`.
216 ///
217 /// This function converts the Rust configuration into the "C" representation
218 /// used by libslirp, handling string conversions and storing necessary Rust
219 /// data to be referenced by the "C" struct.
220 ///
221 /// # Arguments
222 ///
223 /// * `config` - A reference to the Rust `SlirpConfig`.
new(config: &SlirpConfig) -> SlirpConfigs224 pub fn new(config: &SlirpConfig) -> SlirpConfigs {
225 let as_cstring =
226 |s: &Option<String>| s.as_ref().and_then(|s| CString::new(s.as_bytes()).ok());
227 let c_tftp_path = config
228 .tftp_path
229 .as_ref()
230 .and_then(|s| CString::new(s.to_string_lossy().into_owned()).ok());
231 let c_vhostname = as_cstring(&config.vhostname);
232 let c_tftp_server_name = as_cstring(&config.tftp_server_name);
233 let c_bootfile = as_cstring(&config.bootfile);
234 let c_vdomainname = as_cstring(&config.vdomainname);
235
236 let c_host_dns = to_socketaddr_storage(&config.host_dns);
237
238 // Convert to a ptr::null() or a raw ptr to managed
239 // memory. Whenever storing a ptr in "C" Struct using `as_ptr`
240 // this code must have a Rust member is `SlirpConfigs` that
241 // holds the storage.
242 let as_ptr = |p: &Option<CString>| p.as_ref().map_or(std::ptr::null(), |s| s.as_ptr());
243
244 let c_slirp_config = libslirp_sys::SlirpConfig {
245 version: config.version,
246 restricted: config.restricted,
247 in_enabled: config.in_enabled,
248 vnetwork: config.vnetwork.into(),
249 vnetmask: config.vnetmask.into(),
250 vhost: config.vhost.into(),
251 in6_enabled: config.in6_enabled,
252 vprefix_addr6: config.vprefix_addr6.into(),
253 vprefix_len: config.vprefix_len,
254 vhost6: config.vhost6.into(),
255 vhostname: as_ptr(&c_vhostname),
256 tftp_server_name: as_ptr(&c_tftp_server_name),
257 tftp_path: as_ptr(&c_tftp_path),
258 bootfile: as_ptr(&c_bootfile),
259 vdhcp_start: config.vdhcp_start.into(),
260 vnameserver: config.vnameserver.into(),
261 vnameserver6: config.vnameserver6.into(),
262 // TODO: add field
263 vdnssearch: std::ptr::null_mut(),
264 vdomainname: as_ptr(&c_vdomainname),
265 if_mtu: config.if_mtu,
266 if_mru: config.if_mru,
267 disable_host_loopback: config.disable_host_loopback,
268 enable_emu: config.enable_emu,
269 // TODO: add field
270 outbound_addr: std::ptr::null_mut(),
271 // TODO: add field
272 outbound_addr6: std::ptr::null_mut(),
273 disable_dns: config.disable_dns,
274 disable_dhcp: config.disable_dhcp,
275 mfr_id: config.mfr_id,
276 oob_eth_addr: config.oob_eth_addr,
277 http_proxy_on: config.http_proxy_on,
278 host_dns_count: config.host_dns.len(),
279 host_dns: c_host_dns,
280 };
281
282 // Return the "C" struct and Rust members holding the storage
283 // referenced by the "C" struct.
284 SlirpConfigs {
285 c_slirp_config,
286 c_vhostname,
287 c_tftp_server_name,
288 c_bootfile,
289 c_vdomainname,
290 c_tftp_path,
291 c_host_dns,
292 }
293 }
294 }
295
296 #[cfg(test)]
297 mod tests {
298 use super::*;
299 use tokio::runtime::Runtime;
300
301 /// Tests the default values of the `SlirpConfig` struct.
302 #[test]
test_slirp_config_default()303 fn test_slirp_config_default() {
304 let config = SlirpConfig::default();
305
306 assert_eq!(config.version, 5);
307 assert_eq!(config.restricted, 0);
308 assert!(config.in_enabled);
309 assert_eq!(config.vnetwork, Ipv4Addr::new(10, 0, 2, 0));
310 assert_eq!(config.vnetmask, Ipv4Addr::new(255, 255, 255, 0));
311 assert_eq!(config.vhost, Ipv4Addr::new(10, 0, 2, 2));
312 assert!(config.in6_enabled);
313 assert_eq!(config.vprefix_addr6, "fec0::".parse::<Ipv6Addr>().unwrap());
314 assert_eq!(config.vprefix_len, 64);
315 assert_eq!(config.vhost6, "fec0::2".parse::<Ipv6Addr>().unwrap());
316 assert_eq!(config.vhostname, None);
317 assert_eq!(config.tftp_server_name, None);
318 assert_eq!(config.tftp_path, None);
319 assert_eq!(config.bootfile, None);
320 assert_eq!(config.vdhcp_start, Ipv4Addr::new(10, 0, 2, 16));
321 assert_eq!(config.vnameserver, Ipv4Addr::new(10, 0, 2, 3));
322 assert_eq!(config.vnameserver6, "fec0::3".parse::<Ipv6Addr>().unwrap());
323 assert!(config.vdnssearch.is_empty());
324 assert_eq!(config.vdomainname, None);
325 assert_eq!(config.if_mtu, 0);
326 assert_eq!(config.if_mru, 0);
327 assert!(!config.disable_host_loopback);
328 assert!(!config.enable_emu);
329 assert_eq!(config.outbound_addr, None);
330 assert_eq!(config.outbound_addr6, None);
331 assert!(!config.disable_dns);
332 assert!(!config.disable_dhcp);
333 assert_eq!(config.mfr_id, 0);
334 assert_eq!(config.oob_eth_addr, [0; 6]);
335 assert!(!config.http_proxy_on);
336 assert_eq!(config.host_dns.len(), 0);
337 }
338
339 /// Tests the creation of a `SlirpConfigs` instance from a default `SlirpConfig`.
340 #[test]
test_slirp_configs_new()341 fn test_slirp_configs_new() {
342 let rust_config = SlirpConfig::default();
343 let c_configs = SlirpConfigs::new(&rust_config);
344
345 // Check basic field conversions
346 assert_eq!(c_configs.c_slirp_config.version, rust_config.version);
347 assert_eq!(c_configs.c_slirp_config.restricted, rust_config.restricted);
348 assert_eq!(c_configs.c_slirp_config.in_enabled as i32, rust_config.in_enabled as i32);
349
350 // Check string conversions and null pointers
351 assert_eq!(c_configs.c_slirp_config.vhostname, std::ptr::null());
352 assert_eq!(c_configs.c_slirp_config.tftp_server_name, std::ptr::null());
353 }
354
355 /// Tests the `lookup_host_dns` function with different inputs.
356 #[test]
test_lookup_host_dns() -> io::Result<()>357 fn test_lookup_host_dns() -> io::Result<()> {
358 let rt = Runtime::new().unwrap();
359 let results = rt.block_on(lookup_host_dns(""))?;
360 assert_eq!(results.len(), 0);
361
362 let results = rt.block_on(lookup_host_dns("localhost"))?;
363 assert_eq!(results.len(), 1);
364
365 let results = rt.block_on(lookup_host_dns("example.com"))?;
366 assert_eq!(results.len(), 1);
367
368 let results = rt.block_on(lookup_host_dns("localhost,example.com"))?;
369 assert_eq!(results.len(), 2);
370 Ok(())
371 }
372
373 /// Tests the `to_socketaddr_storage` function with an empty input slice.
374 #[test]
test_to_socketaddr_storage_empty_input()375 fn test_to_socketaddr_storage_empty_input() {
376 let dns: [SocketAddr; 0] = [];
377 let result = to_socketaddr_storage(&dns);
378 assert_eq!(result.len(), MAX_DNS_SERVERS);
379 for entry in result {
380 // Assuming `sockaddr_storage::default()` initializes all fields to 0
381 assert_eq!(entry.ss_family, 0);
382 }
383 }
384
385 /// Tests the `to_socketaddr_storage` function with a valid input slice.
386 #[test]
test_to_socketaddr_storage()387 fn test_to_socketaddr_storage() {
388 let dns = ["1.1.1.1:53".parse().unwrap(), "8.8.8.8:53".parse().unwrap()];
389 let result = to_socketaddr_storage(&dns);
390 assert_eq!(result.len(), MAX_DNS_SERVERS);
391 for i in 0..dns.len() {
392 assert_ne!(result[i].ss_family, 0); // Converted addresses should have a non-zero family
393 }
394 for i in dns.len()..MAX_DNS_SERVERS {
395 assert_eq!(result[i].ss_family, 0); // Remaining entries should be default
396 }
397 }
398
399 /// Tests the `to_socketaddr_storage` function with a valid input slice at the maximum allowed size.
400 #[test]
test_to_socketaddr_storage_valid_input_at_max()401 fn test_to_socketaddr_storage_valid_input_at_max() {
402 let dns = [
403 "1.1.1.1:53".parse().unwrap(),
404 "8.8.8.8:53".parse().unwrap(),
405 "9.9.9.9:53".parse().unwrap(),
406 "1.0.0.1:53".parse().unwrap(),
407 ];
408 let result = to_socketaddr_storage(&dns);
409 assert_eq!(result.len(), MAX_DNS_SERVERS);
410 for i in 0..dns.len() {
411 assert_ne!(result[i].ss_family, 0);
412 }
413 }
414
415 /// Tests the `to_socketaddr_storage` function when the input slice exceeds the maximum allowed size.
416 #[test]
test_to_socketaddr_storage_input_exceeds_max()417 fn test_to_socketaddr_storage_input_exceeds_max() {
418 let dns = [
419 "1.1.1.1:53".parse().unwrap(),
420 "8.8.8.8:53".parse().unwrap(),
421 "9.9.9.9:53".parse().unwrap(),
422 "1.0.0.1:53".parse().unwrap(),
423 "1.2.3.4:53".parse().unwrap(), // Extra address
424 ];
425 let result = to_socketaddr_storage(&dns);
426 assert_eq!(result.len(), MAX_DNS_SERVERS);
427 for i in 0..MAX_DNS_SERVERS {
428 assert_ne!(result[i].ss_family, 0);
429 }
430 }
431 }
432