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