• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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