1#[cfg(target_os = "linux")]
9use std::net::{Ipv4Addr, Ipv6Addr};
10
11#[cfg(target_os = "linux")]
12use crate::error::AgentdError;
13use crate::error::AgentdResult;
14
15#[derive(Debug)]
21#[cfg(target_os = "linux")]
22struct NetSpec<'a> {
23 iface: &'a str,
24 mac: [u8; 6],
25 mtu: u16,
26}
27
28#[derive(Debug)]
30#[cfg(target_os = "linux")]
31struct NetIpv4Spec {
32 address: Ipv4Addr,
33 prefix_len: u8,
34 gateway: Ipv4Addr,
35 dns: Option<Ipv4Addr>,
36}
37
38#[derive(Debug)]
40#[cfg(target_os = "linux")]
41struct NetIpv6Spec {
42 address: Ipv6Addr,
43 prefix_len: u8,
44 gateway: Ipv6Addr,
45 dns: Option<Ipv6Addr>,
46}
47
48#[cfg(target_os = "linux")]
57pub fn apply_network_config() -> AgentdResult<()> {
58 let val = match std::env::var(microsandbox_protocol::ENV_NET) {
59 Ok(v) if !v.is_empty() => v,
60 _ => return Ok(()),
61 };
62
63 let net = parse_net(&val)?;
64
65 let ipv4 = match std::env::var(microsandbox_protocol::ENV_NET_IPV4) {
67 Ok(v) if !v.is_empty() => Some(parse_net_ipv4(&v)?),
68 _ => None,
69 };
70
71 let ipv6 = match std::env::var(microsandbox_protocol::ENV_NET_IPV6) {
73 Ok(v) if !v.is_empty() => Some(parse_net_ipv6(&v)?),
74 _ => None,
75 };
76
77 linux::configure_interface(&net, ipv4.as_ref(), ipv6.as_ref())
78}
79
80#[cfg(not(target_os = "linux"))]
82pub fn apply_network_config() -> AgentdResult<()> {
83 Ok(())
84}
85
86#[cfg(target_os = "linux")]
87fn parse_net(val: &str) -> AgentdResult<NetSpec<'_>> {
89 let mut iface = None;
90 let mut mac = None;
91 let mut mtu = 1500u16;
92
93 for part in val.split(',') {
94 if let Some(v) = part.strip_prefix("iface=") {
95 iface = Some(v);
96 } else if let Some(v) = part.strip_prefix("mac=") {
97 mac = Some(parse_mac(v)?);
98 } else if let Some(v) = part.strip_prefix("mtu=") {
99 mtu = v
100 .parse()
101 .map_err(|_| AgentdError::Init(format!("invalid MTU: {v}")))?;
102 } else {
103 return Err(AgentdError::Init(format!("unknown MSB_NET option: {part}")));
104 }
105 }
106
107 let iface = iface.ok_or_else(|| AgentdError::Init("MSB_NET missing iface=".into()))?;
108 let mac = mac.ok_or_else(|| AgentdError::Init("MSB_NET missing mac=".into()))?;
109
110 Ok(NetSpec { iface, mac, mtu })
111}
112
113#[cfg(target_os = "linux")]
114fn parse_net_ipv4(val: &str) -> AgentdResult<NetIpv4Spec> {
116 let mut address = None;
117 let mut prefix_len = None;
118 let mut gateway = None;
119 let mut dns = None;
120
121 for part in val.split(',') {
122 if let Some(v) = part.strip_prefix("addr=") {
123 let (addr, prefix) = parse_cidr_v4(v)?;
124 address = Some(addr);
125 prefix_len = Some(prefix);
126 } else if let Some(v) = part.strip_prefix("gw=") {
127 gateway = Some(
128 v.parse::<Ipv4Addr>()
129 .map_err(|_| AgentdError::Init(format!("invalid IPv4 gateway: {v}")))?,
130 );
131 } else if let Some(v) = part.strip_prefix("dns=") {
132 dns = Some(
133 v.parse::<Ipv4Addr>()
134 .map_err(|_| AgentdError::Init(format!("invalid IPv4 DNS: {v}")))?,
135 );
136 } else {
137 return Err(AgentdError::Init(format!(
138 "unknown MSB_NET_IPV4 option: {part}"
139 )));
140 }
141 }
142
143 let address = address.ok_or_else(|| AgentdError::Init("MSB_NET_IPV4 missing addr=".into()))?;
144 let prefix_len =
145 prefix_len.ok_or_else(|| AgentdError::Init("MSB_NET_IPV4 missing addr=".into()))?;
146 let gateway = gateway.ok_or_else(|| AgentdError::Init("MSB_NET_IPV4 missing gw=".into()))?;
147
148 Ok(NetIpv4Spec {
149 address,
150 prefix_len,
151 gateway,
152 dns,
153 })
154}
155
156#[cfg(target_os = "linux")]
157fn parse_net_ipv6(val: &str) -> AgentdResult<NetIpv6Spec> {
159 let mut address = None;
160 let mut prefix_len = None;
161 let mut gateway = None;
162 let mut dns = None;
163
164 for part in val.split(',') {
165 if let Some(v) = part.strip_prefix("addr=") {
166 let (addr, prefix) = parse_cidr_v6(v)?;
167 address = Some(addr);
168 prefix_len = Some(prefix);
169 } else if let Some(v) = part.strip_prefix("gw=") {
170 gateway = Some(
171 v.parse::<Ipv6Addr>()
172 .map_err(|_| AgentdError::Init(format!("invalid IPv6 gateway: {v}")))?,
173 );
174 } else if let Some(v) = part.strip_prefix("dns=") {
175 dns = Some(
176 v.parse::<Ipv6Addr>()
177 .map_err(|_| AgentdError::Init(format!("invalid IPv6 DNS: {v}")))?,
178 );
179 } else {
180 return Err(AgentdError::Init(format!(
181 "unknown MSB_NET_IPV6 option: {part}"
182 )));
183 }
184 }
185
186 let address = address.ok_or_else(|| AgentdError::Init("MSB_NET_IPV6 missing addr=".into()))?;
187 let prefix_len =
188 prefix_len.ok_or_else(|| AgentdError::Init("MSB_NET_IPV6 missing addr=".into()))?;
189 let gateway = gateway.ok_or_else(|| AgentdError::Init("MSB_NET_IPV6 missing gw=".into()))?;
190
191 Ok(NetIpv6Spec {
192 address,
193 prefix_len,
194 gateway,
195 dns,
196 })
197}
198
199#[cfg(target_os = "linux")]
200fn parse_mac(s: &str) -> AgentdResult<[u8; 6]> {
202 let mut mac = [0u8; 6];
203 let mut len = 0usize;
204 for (i, part) in s.split(':').enumerate() {
205 if i >= 6 {
206 return Err(AgentdError::Init(format!("invalid MAC address: {s}")));
207 }
208 mac[i] = u8::from_str_radix(part, 16)
209 .map_err(|_| AgentdError::Init(format!("invalid MAC octet: {part}")))?;
210 len = i + 1;
211 }
212 if len != 6 {
213 return Err(AgentdError::Init(format!("invalid MAC address: {s}")));
214 }
215 Ok(mac)
216}
217
218#[cfg(target_os = "linux")]
219fn parse_cidr_v4(s: &str) -> AgentdResult<(Ipv4Addr, u8)> {
221 let (addr_str, prefix_str) = s
222 .split_once('/')
223 .ok_or_else(|| AgentdError::Init(format!("invalid IPv4 CIDR (missing /): {s}")))?;
224 let addr = addr_str
225 .parse::<Ipv4Addr>()
226 .map_err(|_| AgentdError::Init(format!("invalid IPv4 address: {addr_str}")))?;
227 let prefix = prefix_str
228 .parse::<u8>()
229 .map_err(|_| AgentdError::Init(format!("invalid IPv4 prefix length: {prefix_str}")))?;
230 if prefix > 32 {
231 return Err(AgentdError::Init(format!(
232 "IPv4 prefix length out of range (0-32): {prefix}"
233 )));
234 }
235 Ok((addr, prefix))
236}
237
238#[cfg(target_os = "linux")]
239fn parse_cidr_v6(s: &str) -> AgentdResult<(Ipv6Addr, u8)> {
241 let (addr_str, prefix_str) = s
242 .rsplit_once('/')
243 .ok_or_else(|| AgentdError::Init(format!("invalid IPv6 CIDR (missing /): {s}")))?;
244 let addr = addr_str
245 .parse::<Ipv6Addr>()
246 .map_err(|_| AgentdError::Init(format!("invalid IPv6 address: {addr_str}")))?;
247 let prefix = prefix_str
248 .parse::<u8>()
249 .map_err(|_| AgentdError::Init(format!("invalid IPv6 prefix length: {prefix_str}")))?;
250 if prefix > 128 {
251 return Err(AgentdError::Init(format!(
252 "IPv6 prefix length out of range (0-128): {prefix}"
253 )));
254 }
255 Ok((addr, prefix))
256}
257
258#[cfg(target_os = "linux")]
263mod linux {
264 use std::net::{Ipv4Addr, Ipv6Addr};
265
266 use crate::error::{AgentdError, AgentdResult};
267
268 use super::{NetIpv4Spec, NetIpv6Spec, NetSpec};
269
270 #[repr(C)]
278 struct IfAddrMsg {
279 ifa_family: u8,
280 ifa_prefixlen: u8,
281 ifa_flags: u8,
282 ifa_scope: u8,
283 ifa_index: u32,
284 }
285
286 #[repr(C)]
287 struct RtMsg {
288 rtm_family: u8,
289 rtm_dst_len: u8,
290 rtm_src_len: u8,
291 rtm_tos: u8,
292 rtm_table: u8,
293 rtm_protocol: u8,
294 rtm_scope: u8,
295 rtm_type: u8,
296 rtm_flags: u32,
297 }
298
299 pub fn configure_interface(
311 net: &NetSpec<'_>,
312 ipv4: Option<&NetIpv4Spec>,
313 ipv6: Option<&NetIpv6Spec>,
314 ) -> AgentdResult<()> {
315 let ifindex = get_ifindex(net.iface)?;
316
317 set_mac_address(net.iface, &net.mac)?;
318 set_mtu(net.iface, net.mtu)?;
319
320 if let Some(v4) = ipv4 {
321 add_address_v4(ifindex, v4.address, v4.prefix_len)?;
322 }
323 if let Some(v6) = ipv6 {
324 add_address_v6(ifindex, v6.address, v6.prefix_len)?;
325 }
326
327 bring_interface_up(net.iface)?;
328
329 if let Some(v4) = ipv4 {
330 add_default_route_v4(v4.gateway)?;
331 }
332 if let Some(v6) = ipv6 {
333 add_default_route_v6(v6.gateway)?;
334 }
335
336 write_resolv_conf(ipv4.and_then(|v| v.dns), ipv6.and_then(|v| v.dns))?;
337
338 Ok(())
339 }
340
341 fn get_ifindex(ifname: &str) -> AgentdResult<u32> {
345 unsafe {
346 let mut ifr: libc::ifreq = std::mem::zeroed();
347 copy_ifname(&mut ifr, ifname)?;
348
349 let sock = socket_fd()?;
350 if libc::ioctl(sock, libc::SIOCGIFINDEX as _, &mut ifr) < 0 {
351 libc::close(sock);
352 return Err(AgentdError::Init(format!(
353 "SIOCGIFINDEX failed for {ifname}: {}",
354 std::io::Error::last_os_error()
355 )));
356 }
357 libc::close(sock);
358
359 Ok(ifr.ifr_ifru.ifru_ifindex as u32)
360 }
361 }
362
363 fn set_mac_address(ifname: &str, mac: &[u8; 6]) -> AgentdResult<()> {
365 unsafe {
366 let mut ifr: libc::ifreq = std::mem::zeroed();
367 copy_ifname(&mut ifr, ifname)?;
368
369 ifr.ifr_ifru.ifru_hwaddr.sa_family = libc::ARPHRD_ETHER;
370 ifr.ifr_ifru.ifru_hwaddr.sa_data[..6].copy_from_slice(&mac.map(|b| b as libc::c_char));
371
372 let sock = socket_fd()?;
373 if libc::ioctl(sock, libc::SIOCSIFHWADDR as _, &ifr) < 0 {
374 libc::close(sock);
375 return Err(AgentdError::Init(format!(
376 "SIOCSIFHWADDR failed for {ifname}: {}",
377 std::io::Error::last_os_error()
378 )));
379 }
380 libc::close(sock);
381 }
382 Ok(())
383 }
384
385 fn set_mtu(ifname: &str, mtu: u16) -> AgentdResult<()> {
387 unsafe {
388 let mut ifr: libc::ifreq = std::mem::zeroed();
389 copy_ifname(&mut ifr, ifname)?;
390 ifr.ifr_ifru.ifru_mtu = mtu as libc::c_int;
391
392 let sock = socket_fd()?;
393 if libc::ioctl(sock, libc::SIOCSIFMTU as _, &ifr) < 0 {
394 libc::close(sock);
395 return Err(AgentdError::Init(format!(
396 "SIOCSIFMTU failed for {ifname}: {}",
397 std::io::Error::last_os_error()
398 )));
399 }
400 libc::close(sock);
401 }
402 Ok(())
403 }
404
405 fn bring_interface_up(ifname: &str) -> AgentdResult<()> {
407 unsafe {
408 let mut ifr: libc::ifreq = std::mem::zeroed();
409 copy_ifname(&mut ifr, ifname)?;
410
411 let sock = socket_fd()?;
412
413 if libc::ioctl(sock, libc::SIOCGIFFLAGS as _, &mut ifr) < 0 {
415 libc::close(sock);
416 return Err(AgentdError::Init(format!(
417 "SIOCGIFFLAGS failed for {ifname}: {}",
418 std::io::Error::last_os_error()
419 )));
420 }
421
422 ifr.ifr_ifru.ifru_flags |= libc::IFF_UP as libc::c_short;
424
425 if libc::ioctl(sock, libc::SIOCSIFFLAGS as _, &ifr) < 0 {
426 libc::close(sock);
427 return Err(AgentdError::Init(format!(
428 "SIOCSIFFLAGS (UP) failed for {ifname}: {}",
429 std::io::Error::last_os_error()
430 )));
431 }
432 libc::close(sock);
433 }
434 Ok(())
435 }
436
437 fn add_address_v4(ifindex: u32, addr: Ipv4Addr, prefix_len: u8) -> AgentdResult<()> {
441 let addr_bytes = addr.octets();
442 netlink_newaddr(ifindex, libc::AF_INET as u8, prefix_len, &addr_bytes).map_err(|e| {
443 AgentdError::Init(format!(
444 "failed to add IPv4 address {addr}/{prefix_len}: {e}"
445 ))
446 })
447 }
448
449 fn add_address_v6(ifindex: u32, addr: Ipv6Addr, prefix_len: u8) -> AgentdResult<()> {
451 let addr_bytes = addr.octets();
452 netlink_newaddr(ifindex, libc::AF_INET6 as u8, prefix_len, &addr_bytes).map_err(|e| {
453 AgentdError::Init(format!(
454 "failed to add IPv6 address {addr}/{prefix_len}: {e}"
455 ))
456 })
457 }
458
459 fn add_default_route_v4(gateway: Ipv4Addr) -> AgentdResult<()> {
461 let gw_bytes = gateway.octets();
462 netlink_newroute(libc::AF_INET as u8, &gw_bytes).map_err(|e| {
463 AgentdError::Init(format!(
464 "failed to add IPv4 default route via {gateway}: {e}"
465 ))
466 })
467 }
468
469 fn add_default_route_v6(gateway: Ipv6Addr) -> AgentdResult<()> {
471 let gw_bytes = gateway.octets();
472 netlink_newroute(libc::AF_INET6 as u8, &gw_bytes).map_err(|e| {
473 AgentdError::Init(format!(
474 "failed to add IPv6 default route via {gateway}: {e}"
475 ))
476 })
477 }
478
479 fn netlink_newaddr(
484 ifindex: u32,
485 family: u8,
486 prefix_len: u8,
487 addr: &[u8],
488 ) -> std::io::Result<()> {
489 let addr_len = addr.len();
490 let is_ipv4 = family == libc::AF_INET as u8;
491
492 let num_rtas = if is_ipv4 { 2 } else { 1 };
494 let rtas_len = rta_space(addr_len) * num_rtas;
495 let msg_len = NLMSG_HDRLEN + IFADDRMSG_LEN + rtas_len;
496 let mut buf = vec![0u8; nlmsg_align(msg_len)];
497
498 let nlh = buf.as_mut_ptr().cast::<libc::nlmsghdr>();
500 unsafe {
501 (*nlh).nlmsg_len = msg_len as u32;
502 (*nlh).nlmsg_type = libc::RTM_NEWADDR;
503 (*nlh).nlmsg_flags =
504 (libc::NLM_F_REQUEST | libc::NLM_F_ACK | libc::NLM_F_CREATE | libc::NLM_F_EXCL)
505 as u16;
506 (*nlh).nlmsg_seq = 1;
507 }
508
509 let ifa = unsafe { buf.as_mut_ptr().add(NLMSG_HDRLEN).cast::<IfAddrMsg>() };
511 unsafe {
512 (*ifa).ifa_family = family;
513 (*ifa).ifa_prefixlen = prefix_len;
514 (*ifa).ifa_flags = 0;
515 (*ifa).ifa_index = ifindex;
516 (*ifa).ifa_scope = libc::RT_SCOPE_UNIVERSE;
517 }
518
519 let mut rta_offset = NLMSG_HDRLEN + IFADDRMSG_LEN;
521 write_rta(&mut buf[rta_offset..], libc::IFA_ADDRESS, addr);
522 rta_offset += rta_space(addr_len);
523
524 if is_ipv4 {
525 write_rta(&mut buf[rta_offset..], libc::IFA_LOCAL, addr);
526 }
527
528 netlink_send(&buf)
529 }
530
531 fn netlink_newroute(family: u8, gateway: &[u8]) -> std::io::Result<()> {
533 let gw_len = gateway.len();
534
535 let rta_len = rta_space(gw_len);
537 let msg_len = NLMSG_HDRLEN + RTMSG_LEN + rta_len;
538 let mut buf = vec![0u8; nlmsg_align(msg_len)];
539
540 let nlh = buf.as_mut_ptr().cast::<libc::nlmsghdr>();
542 unsafe {
543 (*nlh).nlmsg_len = msg_len as u32;
544 (*nlh).nlmsg_type = libc::RTM_NEWROUTE;
545 (*nlh).nlmsg_flags =
546 (libc::NLM_F_REQUEST | libc::NLM_F_ACK | libc::NLM_F_CREATE | libc::NLM_F_EXCL)
547 as u16;
548 (*nlh).nlmsg_seq = 2;
549 }
550
551 let rtm = unsafe { buf.as_mut_ptr().add(NLMSG_HDRLEN).cast::<RtMsg>() };
553 unsafe {
554 (*rtm).rtm_family = family;
555 (*rtm).rtm_dst_len = 0; (*rtm).rtm_src_len = 0;
557 (*rtm).rtm_tos = 0;
558 (*rtm).rtm_table = libc::RT_TABLE_MAIN;
559 (*rtm).rtm_protocol = libc::RTPROT_BOOT;
560 (*rtm).rtm_scope = libc::RT_SCOPE_UNIVERSE;
561 (*rtm).rtm_type = libc::RTN_UNICAST;
562 (*rtm).rtm_flags = 0;
563 }
564
565 let rta_offset = NLMSG_HDRLEN + RTMSG_LEN;
567 write_rta(&mut buf[rta_offset..], libc::RTA_GATEWAY, gateway);
568
569 netlink_send(&buf)
570 }
571
572 fn netlink_send(msg: &[u8]) -> std::io::Result<()> {
574 unsafe {
575 let sock = libc::socket(libc::AF_NETLINK, libc::SOCK_DGRAM, libc::NETLINK_ROUTE);
576 if sock < 0 {
577 return Err(std::io::Error::last_os_error());
578 }
579
580 let mut sa: libc::sockaddr_nl = std::mem::zeroed();
582 sa.nl_family = libc::AF_NETLINK as u16;
583 if libc::bind(
584 sock,
585 (&sa as *const libc::sockaddr_nl).cast(),
586 std::mem::size_of::<libc::sockaddr_nl>() as u32,
587 ) < 0
588 {
589 libc::close(sock);
590 return Err(std::io::Error::last_os_error());
591 }
592
593 if libc::send(sock, msg.as_ptr().cast(), msg.len(), 0) < 0 {
595 libc::close(sock);
596 return Err(std::io::Error::last_os_error());
597 }
598
599 let mut ack_buf = [0u8; 1024];
601 let n = libc::recv(sock, ack_buf.as_mut_ptr().cast(), ack_buf.len(), 0);
602 libc::close(sock);
603
604 if n < 0 {
605 return Err(std::io::Error::last_os_error());
606 }
607
608 if (n as usize) >= NLMSG_HDRLEN + 4 {
611 let nlh = ack_buf.as_ptr().cast::<libc::nlmsghdr>();
612 if (*nlh).nlmsg_type == libc::NLMSG_ERROR as u16 {
613 let err = i32::from_ne_bytes(
614 ack_buf[NLMSG_HDRLEN..NLMSG_HDRLEN + 4].try_into().unwrap(),
615 );
616 if err < 0 {
617 return Err(std::io::Error::from_raw_os_error(-err));
618 }
619 }
620 }
621
622 Ok(())
623 }
624 }
625
626 fn write_resolv_conf(dns_v4: Option<Ipv4Addr>, dns_v6: Option<Ipv6Addr>) -> AgentdResult<()> {
630 if dns_v4.is_none() && dns_v6.is_none() {
631 return Ok(());
632 }
633
634 let mut content = String::new();
635 if let Some(dns) = dns_v4 {
636 content.push_str(&format!("nameserver {dns}\n"));
637 }
638 if let Some(dns) = dns_v6 {
639 content.push_str(&format!("nameserver {dns}\n"));
640 }
641
642 std::fs::write("/etc/resolv.conf", &content)
643 .map_err(|e| AgentdError::Init(format!("failed to write /etc/resolv.conf: {e}")))?;
644
645 Ok(())
646 }
647
648 fn socket_fd() -> AgentdResult<libc::c_int> {
652 let fd = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0) };
653 if fd < 0 {
654 return Err(AgentdError::Init(format!(
655 "failed to create socket: {}",
656 std::io::Error::last_os_error()
657 )));
658 }
659 Ok(fd)
660 }
661
662 fn copy_ifname(ifr: &mut libc::ifreq, ifname: &str) -> AgentdResult<()> {
664 let bytes = ifname.as_bytes();
665 if bytes.len() >= libc::IFNAMSIZ {
666 return Err(AgentdError::Init(format!(
667 "interface name too long: {ifname}"
668 )));
669 }
670 unsafe {
671 std::ptr::copy_nonoverlapping(
672 bytes.as_ptr(),
673 ifr.ifr_name.as_mut_ptr().cast(),
674 bytes.len(),
675 );
676 }
677 Ok(())
678 }
679
680 const NLMSG_HDRLEN: usize = 16;
683 const IFADDRMSG_LEN: usize = 8;
684 const RTMSG_LEN: usize = 12;
685 const RTA_HDRLEN: usize = 4;
686
687 const _: () = assert!(std::mem::size_of::<libc::nlmsghdr>() == NLMSG_HDRLEN);
689 const _: () = assert!(std::mem::size_of::<IfAddrMsg>() == IFADDRMSG_LEN);
690 const _: () = assert!(std::mem::size_of::<RtMsg>() == RTMSG_LEN);
691
692 fn nlmsg_align(len: usize) -> usize {
693 (len + 3) & !3
694 }
695
696 fn rta_space(data_len: usize) -> usize {
697 nlmsg_align(RTA_HDRLEN + data_len)
698 }
699
700 fn write_rta(buf: &mut [u8], rta_type: u16, data: &[u8]) {
702 let rta_len = (RTA_HDRLEN + data.len()) as u16;
703 buf[0..2].copy_from_slice(&rta_len.to_ne_bytes());
704 buf[2..4].copy_from_slice(&rta_type.to_ne_bytes());
705 buf[RTA_HDRLEN..RTA_HDRLEN + data.len()].copy_from_slice(data);
706 }
707}
708
709#[cfg(test)]
714mod tests {
715 use super::*;
716
717 #[test]
718 fn test_parse_net_full() {
719 let spec = parse_net("iface=eth0,mac=02:5a:7b:13:01:02,mtu=1500").unwrap();
720 assert_eq!(spec.iface, "eth0");
721 assert_eq!(spec.mac, [0x02, 0x5a, 0x7b, 0x13, 0x01, 0x02]);
722 assert_eq!(spec.mtu, 1500);
723 }
724
725 #[test]
726 fn test_parse_net_default_mtu() {
727 let spec = parse_net("iface=eth0,mac=02:00:00:00:00:01").unwrap();
728 assert_eq!(spec.mtu, 1500);
729 }
730
731 #[test]
732 fn test_parse_net_missing_iface() {
733 assert!(parse_net("mac=02:00:00:00:00:01").is_err());
734 }
735
736 #[test]
737 fn test_parse_net_missing_mac() {
738 assert!(parse_net("iface=eth0").is_err());
739 }
740
741 #[test]
742 fn test_parse_net_unknown_option() {
743 assert!(parse_net("iface=eth0,mac=02:00:00:00:00:01,bogus=42").is_err());
744 }
745
746 #[test]
747 fn test_parse_net_ipv4() {
748 let spec = parse_net_ipv4("addr=100.96.1.2/30,gw=100.96.1.1,dns=100.96.1.1").unwrap();
749 assert_eq!(spec.address, Ipv4Addr::new(100, 96, 1, 2));
750 assert_eq!(spec.prefix_len, 30);
751 assert_eq!(spec.gateway, Ipv4Addr::new(100, 96, 1, 1));
752 assert_eq!(spec.dns, Some(Ipv4Addr::new(100, 96, 1, 1)));
753 }
754
755 #[test]
756 fn test_parse_net_ipv4_no_dns() {
757 let spec = parse_net_ipv4("addr=10.0.0.2/24,gw=10.0.0.1").unwrap();
758 assert_eq!(spec.dns, None);
759 }
760
761 #[test]
762 fn test_parse_net_ipv4_missing_addr() {
763 assert!(parse_net_ipv4("gw=10.0.0.1").is_err());
764 }
765
766 #[test]
767 fn test_parse_net_ipv6() {
768 let spec = parse_net_ipv6(
769 "addr=fd42:6d73:62:2a::2/64,gw=fd42:6d73:62:2a::1,dns=fd42:6d73:62:2a::1",
770 )
771 .unwrap();
772 assert_eq!(
773 spec.address,
774 "fd42:6d73:62:2a::2".parse::<Ipv6Addr>().unwrap()
775 );
776 assert_eq!(spec.prefix_len, 64);
777 assert_eq!(
778 spec.gateway,
779 "fd42:6d73:62:2a::1".parse::<Ipv6Addr>().unwrap()
780 );
781 assert!(spec.dns.is_some());
782 }
783
784 #[test]
785 fn test_parse_mac_valid() {
786 let mac = parse_mac("02:5a:7b:13:01:02").unwrap();
787 assert_eq!(mac, [0x02, 0x5a, 0x7b, 0x13, 0x01, 0x02]);
788 }
789
790 #[test]
791 fn test_parse_mac_invalid() {
792 assert!(parse_mac("02:5a:7b").is_err());
793 assert!(parse_mac("zz:00:00:00:00:00").is_err());
794 }
795
796 #[test]
797 fn test_parse_cidr_v4() {
798 let (addr, prefix) = parse_cidr_v4("100.96.1.2/30").unwrap();
799 assert_eq!(addr, Ipv4Addr::new(100, 96, 1, 2));
800 assert_eq!(prefix, 30);
801 }
802
803 #[test]
804 fn test_parse_cidr_v6() {
805 let (addr, prefix) = parse_cidr_v6("fd42:6d73:62:2a::2/64").unwrap();
806 assert_eq!(addr, "fd42:6d73:62:2a::2".parse::<Ipv6Addr>().unwrap());
807 assert_eq!(prefix, 64);
808 }
809}