xref: /aosp_15_r20/tools/netsim/rust/libslirp-rs/src/libslirp_config.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
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 const MAX_DNS_SERVERS: usize = SLIRP_MAX_DNS_SERVERS as usize;
26 
27 /// Rust SlirpConfig
28 pub struct SlirpConfig {
29     pub version: u32,
30     pub restricted: i32,
31     pub in_enabled: bool,
32     pub vnetwork: Ipv4Addr,
33     pub vnetmask: Ipv4Addr,
34     pub vhost: Ipv4Addr,
35     pub in6_enabled: bool,
36     pub vprefix_addr6: Ipv6Addr,
37     pub vprefix_len: u8,
38     pub vhost6: Ipv6Addr,
39     pub vhostname: Option<String>,
40     pub tftp_server_name: Option<String>,
41     pub tftp_path: Option<PathBuf>,
42     pub bootfile: Option<String>,
43     pub vdhcp_start: Ipv4Addr,
44     pub vnameserver: Ipv4Addr,
45     pub vnameserver6: Ipv6Addr,
46     pub vdnssearch: Vec<String>,
47     pub vdomainname: Option<String>,
48     pub if_mtu: usize,
49     pub if_mru: usize,
50     pub disable_host_loopback: bool,
51     pub enable_emu: bool,
52     pub outbound_addr: Option<SocketAddrV4>,
53     pub outbound_addr6: Option<SocketAddrV6>,
54     pub disable_dns: bool,
55     pub disable_dhcp: bool,
56     pub mfr_id: u32,
57     pub oob_eth_addr: [u8; 6usize],
58     pub http_proxy_on: bool,
59     pub host_dns: Vec<SocketAddr>,
60 }
61 
62 impl Default for SlirpConfig {
default() -> Self63     fn default() -> Self {
64         SlirpConfig {
65             version: 5,
66             // No restrictions by default
67             restricted: 0,
68             in_enabled: true,
69             // Private network address
70             vnetwork: Ipv4Addr::new(10, 0, 2, 0),
71             vnetmask: Ipv4Addr::new(255, 255, 255, 0),
72             // Default host address
73             vhost: Ipv4Addr::new(10, 0, 2, 2),
74             // IPv6 disabled by default
75             in6_enabled: true,
76             vprefix_addr6: "fec0::".parse().unwrap(),
77             vprefix_len: 64,
78             vhost6: "fec0::2".parse().unwrap(),
79             vhostname: None, // Some("slirp".to_string()),
80             tftp_server_name: None,
81             tftp_path: None,
82             bootfile: None,
83             // DHCP starting address
84             vdhcp_start: Ipv4Addr::new(10, 0, 2, 16),
85             // Public DNS server
86             vnameserver: Ipv4Addr::new(10, 0, 2, 3),
87             vnameserver6: "fec0::3".parse().unwrap(),
88             vdnssearch: Vec::new(),
89             vdomainname: None,
90             // Ethernet MTU
91             if_mtu: 0,
92             // Ethernet MRU
93             if_mru: 0,
94             disable_host_loopback: false,
95             enable_emu: false,
96             outbound_addr: None,
97             outbound_addr6: None,
98             disable_dns: false,
99             disable_dhcp: false,
100             mfr_id: 0,
101             oob_eth_addr: [0; 6usize],
102             http_proxy_on: false,
103             host_dns: Vec::new(),
104         }
105     }
106 }
107 
108 /// Struct to hold a "C" SlirpConfig and the Rust storage that is
109 /// referenced by SlirpConfig.
110 #[allow(dead_code)]
111 pub struct SlirpConfigs {
112     pub c_slirp_config: libslirp_sys::SlirpConfig,
113 
114     // fields that hold the managed storage for "C" struct.
115     c_bootfile: Option<CString>,
116     c_tftp_server_name: Option<CString>,
117     c_vdomainname: Option<CString>,
118     c_vhostname: Option<CString>,
119     c_tftp_path: Option<CString>,
120     c_host_dns: [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS],
121     // TODO: add other fields
122 }
123 
lookup_host_dns(host_dns: &str) -> io::Result<Vec<SocketAddr>>124 pub async fn lookup_host_dns(host_dns: &str) -> io::Result<Vec<SocketAddr>> {
125     let mut set = tokio::task::JoinSet::new();
126     if host_dns.is_empty() {
127         return Ok(Vec::new());
128     }
129 
130     for addr in host_dns.split(",") {
131         set.spawn(tokio::net::lookup_host(format!("{addr}:0")));
132     }
133 
134     let mut addrs = Vec::new();
135     while let Some(result) = set.join_next().await {
136         addrs.push(result??.next().ok_or(io::Error::from(io::ErrorKind::NotFound))?);
137     }
138     Ok(addrs)
139 }
140 
to_socketaddr_storage(dns: &[SocketAddr]) -> [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS]141 fn to_socketaddr_storage(dns: &[SocketAddr]) -> [libslirp_sys::sockaddr_storage; MAX_DNS_SERVERS] {
142     let mut result = [libslirp_sys::sockaddr_storage::default(); MAX_DNS_SERVERS];
143     if dns.len() > MAX_DNS_SERVERS {
144         warn!("Too many DNS servers, only keeping the first {} ones", MAX_DNS_SERVERS);
145     }
146     for i in 0..usize::min(dns.len(), MAX_DNS_SERVERS) {
147         result[i] = dns[i].into();
148     }
149     result
150 }
151 
152 impl SlirpConfigs {
new(config: &SlirpConfig) -> SlirpConfigs153     pub fn new(config: &SlirpConfig) -> SlirpConfigs {
154         let as_cstring =
155             |s: &Option<String>| s.as_ref().and_then(|s| CString::new(s.as_bytes()).ok());
156         let c_tftp_path = config
157             .tftp_path
158             .as_ref()
159             .and_then(|s| CString::new(s.to_string_lossy().into_owned()).ok());
160         let c_vhostname = as_cstring(&config.vhostname);
161         let c_tftp_server_name = as_cstring(&config.tftp_server_name);
162         let c_bootfile = as_cstring(&config.bootfile);
163         let c_vdomainname = as_cstring(&config.vdomainname);
164 
165         let c_host_dns = to_socketaddr_storage(&config.host_dns);
166 
167         // Convert to a ptr::null() or a raw ptr to managed
168         // memory. Whenever storing a ptr in "C" Struct using `as_ptr`
169         // this code must have a Rust member is `SlirpConfigs` that
170         // holds the storage.
171         let as_ptr = |p: &Option<CString>| p.as_ref().map_or(std::ptr::null(), |s| s.as_ptr());
172 
173         let c_slirp_config = libslirp_sys::SlirpConfig {
174             version: config.version,
175             restricted: config.restricted,
176             in_enabled: config.in_enabled,
177             vnetwork: config.vnetwork.into(),
178             vnetmask: config.vnetmask.into(),
179             vhost: config.vhost.into(),
180             in6_enabled: config.in6_enabled,
181             vprefix_addr6: config.vprefix_addr6.into(),
182             vprefix_len: config.vprefix_len,
183             vhost6: config.vhost6.into(),
184             vhostname: as_ptr(&c_vhostname),
185             tftp_server_name: as_ptr(&c_tftp_server_name),
186             tftp_path: as_ptr(&c_tftp_path),
187             bootfile: as_ptr(&c_bootfile),
188             vdhcp_start: config.vdhcp_start.into(),
189             vnameserver: config.vnameserver.into(),
190             vnameserver6: config.vnameserver6.into(),
191             // TODO: add field
192             vdnssearch: std::ptr::null_mut(),
193             vdomainname: as_ptr(&c_vdomainname),
194             if_mtu: config.if_mtu,
195             if_mru: config.if_mru,
196             disable_host_loopback: config.disable_host_loopback,
197             enable_emu: config.enable_emu,
198             // TODO: add field
199             outbound_addr: std::ptr::null_mut(),
200             // TODO: add field
201             outbound_addr6: std::ptr::null_mut(),
202             disable_dns: config.disable_dns,
203             disable_dhcp: config.disable_dhcp,
204             mfr_id: config.mfr_id,
205             oob_eth_addr: config.oob_eth_addr,
206             http_proxy_on: config.http_proxy_on,
207             host_dns_count: config.host_dns.len(),
208             host_dns: c_host_dns,
209         };
210 
211         // Return the "C" struct and Rust members holding the storage
212         // referenced by the "C" struct.
213         SlirpConfigs {
214             c_slirp_config,
215             c_vhostname,
216             c_tftp_server_name,
217             c_bootfile,
218             c_vdomainname,
219             c_tftp_path,
220             c_host_dns,
221         }
222     }
223 }
224 
225 #[cfg(test)]
226 mod tests {
227     use super::*;
228     use tokio::runtime::Runtime;
229 
230     #[test]
test_slirp_config_default()231     fn test_slirp_config_default() {
232         let config = SlirpConfig::default();
233 
234         assert_eq!(config.version, 5);
235         assert_eq!(config.restricted, 0);
236         assert!(config.in_enabled);
237         assert_eq!(config.vnetwork, Ipv4Addr::new(10, 0, 2, 0));
238         assert_eq!(config.vnetmask, Ipv4Addr::new(255, 255, 255, 0));
239         assert_eq!(config.vhost, Ipv4Addr::new(10, 0, 2, 2));
240         assert!(config.in6_enabled);
241         assert_eq!(config.vprefix_addr6, "fec0::".parse::<Ipv6Addr>().unwrap());
242         assert_eq!(config.vprefix_len, 64);
243         assert_eq!(config.vhost6, "fec0::2".parse::<Ipv6Addr>().unwrap());
244         assert_eq!(config.vhostname, None);
245         assert_eq!(config.tftp_server_name, None);
246         assert_eq!(config.tftp_path, None);
247         assert_eq!(config.bootfile, None);
248         assert_eq!(config.vdhcp_start, Ipv4Addr::new(10, 0, 2, 16));
249         assert_eq!(config.vnameserver, Ipv4Addr::new(10, 0, 2, 3));
250         assert_eq!(config.vnameserver6, "fec0::3".parse::<Ipv6Addr>().unwrap());
251         assert!(config.vdnssearch.is_empty());
252         assert_eq!(config.vdomainname, None);
253         assert_eq!(config.if_mtu, 0);
254         assert_eq!(config.if_mru, 0);
255         assert!(!config.disable_host_loopback);
256         assert!(!config.enable_emu);
257         assert_eq!(config.outbound_addr, None);
258         assert_eq!(config.outbound_addr6, None);
259         assert!(!config.disable_dns);
260         assert!(!config.disable_dhcp);
261         assert_eq!(config.mfr_id, 0);
262         assert_eq!(config.oob_eth_addr, [0; 6]);
263         assert!(!config.http_proxy_on);
264         assert_eq!(config.host_dns.len(), 0);
265     }
266 
267     #[test]
test_slirp_configs_new()268     fn test_slirp_configs_new() {
269         let rust_config = SlirpConfig::default();
270         let c_configs = SlirpConfigs::new(&rust_config);
271 
272         // Check basic field conversions
273         assert_eq!(c_configs.c_slirp_config.version, rust_config.version);
274         assert_eq!(c_configs.c_slirp_config.restricted, rust_config.restricted);
275         assert_eq!(c_configs.c_slirp_config.in_enabled as i32, rust_config.in_enabled as i32);
276 
277         // Check string conversions and null pointers
278         assert_eq!(c_configs.c_slirp_config.vhostname, std::ptr::null());
279         assert_eq!(c_configs.c_slirp_config.tftp_server_name, std::ptr::null());
280     }
281 
282     #[test]
test_lookup_host_dns() -> io::Result<()>283     fn test_lookup_host_dns() -> io::Result<()> {
284         let rt = Runtime::new().unwrap();
285         let results = rt.block_on(lookup_host_dns(""))?;
286         assert_eq!(results.len(), 0);
287 
288         let results = rt.block_on(lookup_host_dns("localhost"))?;
289         assert_eq!(results.len(), 1);
290 
291         let results = rt.block_on(lookup_host_dns("example.com"))?;
292         assert_eq!(results.len(), 1);
293 
294         let results = rt.block_on(lookup_host_dns("localhost,example.com"))?;
295         assert_eq!(results.len(), 2);
296         Ok(())
297     }
298 
299     #[test]
test_to_socketaddr_storage_empty_input()300     fn test_to_socketaddr_storage_empty_input() {
301         let dns: [SocketAddr; 0] = [];
302         let result = to_socketaddr_storage(&dns);
303         assert_eq!(result.len(), MAX_DNS_SERVERS);
304         for entry in result {
305             // Assuming `sockaddr_storage::default()` initializes all fields to 0
306             assert_eq!(entry.ss_family, 0);
307         }
308     }
309 
310     #[test]
test_to_socketaddr_storage()311     fn test_to_socketaddr_storage() {
312         let dns = ["1.1.1.1:53".parse().unwrap(), "8.8.8.8:53".parse().unwrap()];
313         let result = to_socketaddr_storage(&dns);
314         assert_eq!(result.len(), MAX_DNS_SERVERS);
315         for i in 0..dns.len() {
316             assert_ne!(result[i].ss_family, 0); // Converted addresses should have a non-zero family
317         }
318         for i in dns.len()..MAX_DNS_SERVERS {
319             assert_eq!(result[i].ss_family, 0); // Remaining entries should be default
320         }
321     }
322 
323     #[test]
test_to_socketaddr_storage_valid_input_at_max()324     fn test_to_socketaddr_storage_valid_input_at_max() {
325         let dns = [
326             "1.1.1.1:53".parse().unwrap(),
327             "8.8.8.8:53".parse().unwrap(),
328             "9.9.9.9:53".parse().unwrap(),
329             "1.0.0.1:53".parse().unwrap(),
330         ];
331         let result = to_socketaddr_storage(&dns);
332         assert_eq!(result.len(), MAX_DNS_SERVERS);
333         for i in 0..dns.len() {
334             assert_ne!(result[i].ss_family, 0);
335         }
336     }
337 
338     #[test]
test_to_socketaddr_storage_input_exceeds_max()339     fn test_to_socketaddr_storage_input_exceeds_max() {
340         let dns = [
341             "1.1.1.1:53".parse().unwrap(),
342             "8.8.8.8:53".parse().unwrap(),
343             "9.9.9.9:53".parse().unwrap(),
344             "1.0.0.1:53".parse().unwrap(),
345             "1.2.3.4:53".parse().unwrap(), // Extra address
346         ];
347         let result = to_socketaddr_storage(&dns);
348         assert_eq!(result.len(), MAX_DNS_SERVERS);
349         for i in 0..MAX_DNS_SERVERS {
350             assert_ne!(result[i].ss_family, 0);
351         }
352     }
353 }
354