1use crate::nvlist::{NvDataType, NvHeader, NvPair, Value, NVP};
4use libc::{free, malloc, sockaddr_in6};
5use rusty_doors::{door_call, door_callp};
6use std::collections::HashMap;
7use std::convert::TryInto;
8use std::ffi::CStr;
9use std::fs::File;
10use std::mem::{align_of, size_of};
11use std::net::{Ipv4Addr, Ipv6Addr};
12use std::os::raw::c_char;
13use std::os::unix::io::AsRawFd;
14use std::ptr;
15use std::str::FromStr;
16use tracing::{debug, trace, warn};
17
18#[derive(Debug)]
19#[repr(i32)]
20pub enum IpmgmtCmd {
21 Unset = 0,
22 SetProp = 1,
23 SetIf = 2,
24 SetAddr = 3,
25 GetProp = 4,
26 GetIf = 5,
27 GetAddr = 6,
28 ResetIf = 7,
29 ResetAddr = 8,
30 ResetProp = 9,
31 InitIf = 10,
32 AddrobjLookupAdd = 11,
33 AddrobjSetLifnum = 12,
34 AddrobjAdd = 13,
35 Lif2Addrobj = 14,
36 AobjName2Addrobj = 15,
37}
38
39#[derive(Debug)]
40#[repr(C)]
41pub struct IpmgmtGetAddr {
42 pub cmd: IpmgmtCmd,
43 pub flags: u32,
44 pub ifname: [u8; 32],
45 pub family: u16,
46 pub objname: [u8; 64],
47}
48
49#[derive(Debug)]
50#[repr(C)]
51pub struct IpmgmtSetAddr {
52 pub cmd: IpmgmtCmd,
53 pub flags: u32,
54
55 pub nvlsize: u32,
57}
58
59impl Default for IpmgmtGetAddr {
60 fn default() -> Self {
61 IpmgmtGetAddr {
62 cmd: IpmgmtCmd::GetAddr,
63 flags: 0,
64 ifname: [0; 32],
65 family: 0,
66 objname: [0; 64],
67 }
68 }
69}
70
71#[derive(Debug, Default)]
72#[repr(C)]
73pub struct IpmgmtRval {
74 pub err: i32,
75}
76
77#[derive(Debug, Default)]
78#[repr(C)]
79pub struct IpmgmtGetRval {
80 pub err: i32,
81 pub nval_size: u32,
82 }
84
85pub const LIFNAMSIZ: u32 = 32;
86pub const IPADM_AOBJ_USTRSIZ: u32 = 32;
87pub const IPADM_AOBJSIZ: u32 = LIFNAMSIZ + IPADM_AOBJ_USTRSIZ;
88
89#[derive(Debug, Default, Copy, Clone)]
90#[repr(i32)]
91pub enum AddrType {
92 #[default]
93 AddrNone,
94 Static,
95 Ipv6Addrconf,
96 Dhcp,
97}
98
99#[repr(C)]
100pub struct IpmgmtAobjopArg {
101 pub cmd: IpmgmtCmd,
102 pub flags: u32,
103 pub objname: [c_char; IPADM_AOBJSIZ as usize],
104 pub ifname: [c_char; LIFNAMSIZ as usize],
105 pub lnum: i32,
106 pub family: u16,
107 pub atype: AddrType,
108}
109
110#[repr(C)]
111#[derive(Copy, Clone)]
112pub struct IpmgmtAobjopRval {
113 pub err: i32,
114 pub objname: [c_char; IPADM_AOBJSIZ as usize],
115 pub ifname: [c_char; LIFNAMSIZ as usize],
116 pub lnum: i32,
117 pub family: u16,
118 pub flags: u32,
119 pub atype: AddrType,
120 pub atype_cache: IpmgmtAddrTypeCache,
121}
122
123impl Default for IpmgmtAobjopRval {
124 fn default() -> Self {
125 IpmgmtAobjopRval {
126 err: 0,
127 objname: [0; IPADM_AOBJSIZ as usize],
128 ifname: [0; LIFNAMSIZ as usize],
129 lnum: 0,
130 family: 0,
131 flags: 0,
132 atype: AddrType::default(),
133 atype_cache: IpmgmtAddrTypeCache::default(),
134 }
135 }
136}
137
138#[repr(C)]
139#[derive(Copy, Clone)]
140pub union IpmgmtAddrTypeCache {
141 pub ipv6_cache: IpmgmtIpv6Cache,
142 pub dhcp_cache: IpmgmtDhcpCache,
143}
144
145impl Default for IpmgmtAddrTypeCache {
146 fn default() -> Self {
147 IpmgmtAddrTypeCache {
148 ipv6_cache: IpmgmtIpv6Cache::default(),
149 }
150 }
151}
152
153#[repr(C)]
155#[derive(Debug, Copy, Clone)]
156pub enum BooleanT {
157 False,
158 True,
159}
160
161#[repr(C)]
162#[derive(Copy, Clone)]
163pub struct IpmgmtIpv6Cache {
164 pub linklocal: BooleanT,
165 pub ifid: sockaddr_in6,
166}
167
168impl Default for IpmgmtIpv6Cache {
169 fn default() -> Self {
170 IpmgmtIpv6Cache {
171 linklocal: BooleanT::False,
172 ifid: sockaddr_in6 {
173 sin6_family: 0,
174 sin6_port: 0,
175 sin6_flowinfo: 0,
176 sin6_addr: libc::in6_addr { s6_addr: [0; 16] },
177 sin6_scope_id: 0,
178 ..unsafe { std::mem::zeroed() }
179 },
180 }
181 }
182}
183
184#[repr(C)]
185#[derive(Debug, Copy, Clone)]
186pub struct IpmgmtDhcpCache {
187 pub reqhost: [c_char; 256usize],
188}
189
190impl Default for IpmgmtDhcpCache {
191 fn default() -> Self {
192 IpmgmtDhcpCache { reqhost: [0; 256] }
193 }
194}
195
196#[derive(Debug)]
197#[repr(C)]
198pub struct IpmgmtAddrArg {
199 pub cmd: IpmgmtCmd,
200 pub flags: u32,
201 pub objname: [c_char; IPADM_AOBJSIZ as usize],
202 pub lnum: u32,
203}
204
205#[derive(Debug)]
206pub struct IpInfo {
207 pub if_name: String,
208 pub addr_obj: String,
209 pub properties: IpProperties,
210}
211
212#[derive(Debug)]
213pub enum IpProperties {
214 Dhcp(DhcpProperties),
215 Intfid(Intfid),
216 V4Static(V4Static),
217 V6Static(V6Static),
218}
219
220#[derive(Debug)]
221pub struct DhcpProperties {
222 pub parameters: Option<DhcpClientParameters>,
223 pub reqhost: Option<String>,
224}
225
226#[derive(Debug)]
227pub struct DhcpClientParameters {
228 pub wait: i32,
229 pub primary: bool,
230}
231
232#[derive(Debug)]
233pub struct V4Static {
234 pub addr: Ipv4Addr,
235}
236
237#[derive(Debug)]
238pub struct V6Static {
239 pub addr: Ipv6Addr,
240}
241
242#[derive(Debug)]
243pub struct Intfid {
244 pub prefix_len: u32,
245 pub addr: Ipv6Addr,
246 pub stateless: bool,
247 pub stateful: bool,
248}
249
250pub fn get_persistent_ipinfo(
251) -> Result<HashMap<String, HashMap<String, IpInfo>>, String> {
252 unsafe {
253 let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")
256 .map_err(|e| format!("door open: {}", e))?;
257
258 let mut response: *mut IpmgmtGetRval =
261 malloc(std::mem::size_of::<IpmgmtGetRval>()) as *mut IpmgmtGetRval;
262
263 let request = IpmgmtGetAddr {
264 ..Default::default()
265 };
266 let resp: *mut IpmgmtGetRval = door_callp(
267 f.as_raw_fd(),
268 request,
269 ptr::NonNull::new(&mut response).unwrap(), );
271 trace!("got {} bytes of nval", (*resp).nval_size);
272
273 let nvh = (response.offset(1) as *const IpmgmtGetRval) as *mut NvHeader;
276 trace!("found nvl header {:?}", *nvh);
277
278 let sk = ((nvh.offset(1) as *const NvHeader) as *const u8).offset(0);
283 let skipped = std::slice::from_raw_parts(sk, 8);
284 warn!("skipping {:x?}", skipped);
285 let p = ((nvh.offset(1) as *const NvHeader) as *const u8).offset(8);
286
287 let end = (*resp).nval_size as i32;
296
297 let (nvps, _) = extract_nvps(p, end);
300 trace!("NVPs: {:#?}", nvps);
301
302 free(resp as *mut std::os::raw::c_void);
303
304 Ok(handle_nvps(&nvps))
305 }
306}
307
308fn handle_nvps(
309 nvps: &[NVP<'static>],
310) -> HashMap<String, HashMap<String, IpInfo>> {
311 let mut result = HashMap::new();
312
313 for nvp in nvps.iter() {
314 let ip_info = match handle_nvp(nvp) {
315 Some(ip_info) => ip_info,
316 None => continue,
317 };
318
319 match result.get_mut(&ip_info.if_name) {
320 None => {
321 let mut hm = HashMap::new();
322 let k = ip_info.if_name.clone();
323 hm.insert(ip_info.addr_obj.clone(), ip_info);
324 result.insert(k, hm);
325 }
326 Some(hm) => match hm.get_mut(&ip_info.addr_obj.clone()) {
327 Some(ipi) => {
328 if let IpProperties::Dhcp(dcp) = &mut ipi.properties {
329 if let IpProperties::Dhcp(dcpi) = ip_info.properties {
330 if let Some(ps) = dcpi.parameters {
331 dcp.parameters = Some(ps)
332 }
333 if let Some(rh) = dcpi.reqhost {
334 dcp.reqhost = Some(rh)
335 }
336 }
337 }
338 }
339 None => {
340 hm.insert(ip_info.addr_obj.clone(), ip_info);
341 }
342 },
343 }
344 }
345
346 debug!("ipinfos: {:#?}", result);
347
348 result
349}
350
351fn handle_nvp(nvp: &NVP<'static>) -> Option<IpInfo> {
354 let parts = match &nvp.value {
355 Value::NvList(parts) => parts,
356 _ => {
357 warn!(
360 "Disaggregated ipmgmt property detected: {}. Skipping",
361 nvp.name
362 );
363 return None;
364 }
365 };
366
367 let mut if_name: Option<String> = None;
368 let mut addr_obj: Option<String> = None;
369 let mut ip_properties: Option<IpProperties> = None;
370
371 for part in parts.iter() {
372 if part.name == "_ifname" {
373 if let Value::Str(s) = part.value {
374 if_name = Some(s.to_string());
375 }
376 }
377
378 if part.name == "_aobjname" {
379 if let Value::Str(s) = part.value {
380 addr_obj = Some(s.to_string());
381 }
382 }
383
384 if part.name == "_dhcp" {
385 if let Value::NvList(vs) = &part.value {
386 let mut dcp = DhcpClientParameters {
387 wait: -1,
388 primary: false,
389 };
390 for v in vs.iter() {
391 if v.name == "wait" {
392 if let Value::Int32(i) = v.value {
393 dcp.wait = i;
394 }
395 }
396 if v.name == "primary" {
397 if let Value::Boolean(b) = v.value {
398 dcp.primary = b;
399 }
400 }
401 }
402 match &mut ip_properties {
403 Some(props) => match props {
404 IpProperties::Dhcp(props) => {
405 props.parameters = Some(dcp)
406 }
407 _ => {
408 warn!("dhcp client params in non-dhcp record");
409 }
410 },
411 None => {
412 ip_properties =
413 Some(IpProperties::Dhcp(DhcpProperties {
414 parameters: Some(dcp),
415 reqhost: None,
416 }));
417 }
418 }
419 };
420 }
421
422 if part.name == "reqhost" {
423 if let Value::Str(s) = &part.value {
424 match &mut ip_properties {
425 Some(props) => match props {
426 IpProperties::Dhcp(props) => {
427 props.reqhost = Some(s.to_string());
428 }
429 _ => {
430 warn!("dhcp reqhost found in non-dhcp record")
431 }
432 },
433 None => {
434 ip_properties =
435 Some(IpProperties::Dhcp(DhcpProperties {
436 parameters: None,
437 reqhost: Some(s.to_string()),
438 }));
439 }
440 }
441 }
442 }
443
444 if part.name == "_intfid" {
445 if let Value::NvList(vs) = &part.value {
446 let mut intfid = Intfid {
447 prefix_len: 0,
448 addr: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
449 stateless: false,
450 stateful: false,
451 };
452 for v in vs.iter() {
453 if v.name == "prefixlen" {
454 if let Value::Uint32(u) = v.value {
455 intfid.prefix_len = u;
456 }
457 }
458 if v.name == "_addr" {
459 if let Value::Uint8Array(u) = v.value {
460 intfid.addr = match u.try_into()
461 as Result<
462 [u8; 16],
463 <&[u8] as TryInto<[u8; 16]>>::Error,
464 > {
465 Ok(addr) => Ipv6Addr::from(addr),
466 Err(_) => continue,
467 }
468 }
469 }
470 if v.name == "_stateless" {
471 if let Value::Str(b) = v.value {
472 if b == "yes" {
473 intfid.stateless = true;
474 }
475 }
476 }
477 if v.name == "_stateful" {
478 if let Value::Str(b) = v.value {
479 if b == "yes" {
480 intfid.stateful = true
481 }
482 }
483 }
484 }
485 match &mut ip_properties {
486 Some(_) => {
487 warn!("duplicate v6 properties");
488 }
489 None => {
490 ip_properties = Some(IpProperties::Intfid(intfid));
491 }
492 }
493 }
494 }
495
496 if part.name == "_ipv4addr" {
497 if let Value::NvList(vs) = &part.value {
498 for v in vs.iter() {
499 if let Value::Str(s) = &v.value {
500 match Ipv4Addr::from_str(s) {
501 Ok(addr) => {
502 ip_properties =
503 Some(IpProperties::V4Static(V4Static {
504 addr,
505 }));
506 }
507 _ => {
508 warn!("bad ipv4 address: {}", s)
509 }
510 }
511 }
512 }
513 }
514 }
515
516 if part.name == "_ipv6addr" {
517 if let Value::NvList(vs) = &part.value {
518 for v in vs.iter() {
519 if let Value::Str(s) = &v.value {
520 match Ipv6Addr::from_str(s) {
521 Ok(addr) => {
522 ip_properties =
523 Some(IpProperties::V6Static(V6Static {
524 addr,
525 }));
526 }
527 _ => {
528 warn!("bad ipv6 address: {}", s)
529 }
530 }
531 }
532 }
533 }
534 }
535 }
536
537 match (if_name, addr_obj, ip_properties) {
538 (Some(n), Some(a), Some(p)) => Some(IpInfo {
539 if_name: n,
540 addr_obj: a,
541 properties: p,
542 }),
543 _ => None,
544 }
545}
546
547fn extract_nvps(mut p: *const u8, size: i32) -> (Vec<NVP<'static>>, i32) {
548 let mut nvps = Vec::new();
549 let mut consumed = 0;
550
551 unsafe {
552 loop {
555 let sz = *(p as *const i32);
556 if sz == 0 {
557 trace!("found zero sized nvpair, return from extract");
558 consumed += 4;
559 return (nvps, consumed);
561 }
562 let nvp = p as *const NvPair;
563 let nv = match extract_nvp(nvp) {
564 Ok(nv) => nv,
565 Err(e) => {
566 warn!("nv extraction failed: {}", e);
567 continue;
568 }
569 };
570
571 p = p.add(sz as usize);
572 p = p.add(p.align_offset(align_of::<u32>()));
573 consumed += sz;
574 trace!("consumed {}", consumed);
575 if consumed >= size {
576 break;
577 }
578
579 match nv.value {
580 Value::NvList(_) => {
581 let (embedded_nvps, embedded_consumed) =
582 extract_nvps(p, size - consumed);
583
584 nvps.push(NVP {
585 name: nv.name,
586 value: Value::NvList(embedded_nvps),
587 });
588 consumed += embedded_consumed;
589 p = p.add(embedded_consumed as usize);
590 }
591 _ => {
592 nvps.push(nv);
593 }
594 }
595 }
596 }
597
598 (nvps, consumed)
599}
600
601fn extract_nvp(nvp: *const NvPair) -> Result<NVP<'static>, String> {
602 let mut result = NVP {
603 name: "?",
604 value: Value::Unknown,
605 };
606
607 unsafe {
608 trace!("nvp: {:?}", *nvp);
611 let p = (nvp.offset(1) as *const u8) as *mut u8;
612 let name = {
613 let slice =
614 std::slice::from_raw_parts(p, (*nvp).name_size as usize);
615 let cstr = std::ffi::CStr::from_bytes_with_nul_unchecked(slice);
616 match cstr.to_str() {
617 Ok(s) => s,
618 Err(e) => return Err(format!("name to string: {}", e)),
619 }
620 };
621 result.name = name;
622 trace!(" name: {:?}", name);
623
624 let mut v = p.offset((*nvp).name_size as isize);
627 v = v.add(v.align_offset(align_of::<u32>()));
628
629 match (*nvp).typ {
630 NvDataType::Str => {
631 let slice = std::slice::from_raw_parts(
632 v,
633 ((*nvp).size
634 - (*nvp).name_size as i32
635 - size_of::<NvPair>() as i32
636 - 1) as usize,
637 );
638 let cstr = std::ffi::CStr::from_bytes_with_nul_unchecked(slice);
639 let decoded = cstr.to_str().unwrap_or("<undecodable value>");
640 let trimmed = decoded.trim_matches('\u{0}');
643 result.value = Value::Str(trimmed);
644 trace!(" value: {:?}", trimmed);
645 }
646
647 NvDataType::BooleanValue => {
648 let b = *(v as *const bool);
649 result.value = Value::Boolean(b);
650 trace!(" value: {}", b);
651 }
652
653 NvDataType::Int32 => {
654 let i = *(v as *const i32);
655 result.value = Value::Int32(i);
656 trace!(" value: {}", i);
657 }
658
659 NvDataType::Uint32 => {
660 let u = *(v as *const u32);
661 result.value = Value::Uint32(u);
662 trace!(" value: {}", u);
663 }
664
665 NvDataType::Uint8Array => {
666 let ua =
667 std::slice::from_raw_parts(v, (*nvp).value_count as usize);
668 result.value = Value::Uint8Array(ua);
669 trace!(" value: {:x?}", ua);
670 }
671
672 NvDataType::NvList => {
673 result.value = Value::NvList(Vec::new());
674 }
675
676 _ => {}
677 }
678 }
679
680 Ok(result)
681}
682
683pub fn ifname_to_addrobj(
685 mut if_name: &str,
686 addr_family: u16,
687) -> Result<(String, String), String> {
688 let parts: Vec<&str> = if_name.split(':').collect();
689 let num = match parts.len() {
690 2 => match parts[1].parse::<i32>() {
691 Ok(n) => {
692 if_name = parts[0];
693 n
694 }
695 Err(_) => 0,
696 },
697 _ => 0,
698 };
699
700 let mut ia_ifname = [0; 32usize];
701 for (i, c) in if_name.chars().enumerate() {
702 ia_ifname[i] = c as std::os::raw::c_char;
703 }
704
705 let request = crate::sys::ipmgmt_aobjop_arg_t {
706 ia_ifname,
707 ia_cmd: crate::sys::ipmgmt_door_cmd_type_t_IPMGMT_CMD_LIF2ADDROBJ,
708 ia_flags: 0,
709 ia_aobjname: [0; 64usize],
710 ia_lnum: num,
711 ia_family: addr_family,
712 ia_atype: crate::sys::ipadm_addr_type_t_IPADM_ADDR_NONE,
713 };
714
715 let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")
716 .map_err(|e| format!("door open: {}", e))?;
717
718 let resp: crate::sys::ipmgmt_aobjop_rval_t =
719 door_call(f.as_raw_fd(), request);
720
721 let objname = unsafe {
722 CStr::from_ptr(resp.ir_aobjname.as_ptr())
723 .to_str()
724 .map_err(|e| format!("abojname cstr to str: {}", e))?
725 .to_string()
726 };
727
728 let source = match resp.ir_atype {
729 crate::sys::ipadm_addr_type_t_IPADM_ADDR_NONE => "none",
730 crate::sys::ipadm_addr_type_t_IPADM_ADDR_STATIC => "static",
731 crate::sys::ipadm_addr_type_t_IPADM_ADDR_IPV6_ADDRCONF => "addrconf",
732 crate::sys::ipadm_addr_type_t_IPADM_ADDR_DHCP => "dhcp",
733 _ => "?",
734 };
735
736 Ok((objname, source.to_string()))
737}
738
739pub fn addrobjname_to_addrobj(
742 aobj_name: &str,
743) -> Result<(String, String, u16, String, i32), String> {
744 let mut request = crate::sys::ipmgmt_aobjop_arg_t {
745 ia_cmd: crate::sys::ipmgmt_door_cmd_type_t_IPMGMT_CMD_AOBJNAME2ADDROBJ,
746 ia_flags: 0,
747 ia_aobjname: [0; 64usize],
748 ia_ifname: [0; 32usize],
749 ia_lnum: 0,
750 ia_family: 0,
751 ia_atype: crate::sys::ipadm_addr_type_t_IPADM_ADDR_NONE,
752 };
753 for (i, c) in aobj_name.chars().enumerate() {
754 request.ia_aobjname[i] = c as std::os::raw::c_char;
755 }
756
757 let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")
758 .map_err(|e| format!("door open: {}", e))?;
759
760 let resp: crate::sys::ipmgmt_aobjop_rval_t =
761 door_call(f.as_raw_fd(), request);
762
763 let ifname = unsafe {
764 CStr::from_ptr(resp.ir_ifname.as_ptr())
765 .to_str()
766 .map_err(|e| format!("abojname cstr to str: {}", e))?
767 .to_string()
768 };
769
770 let source = match resp.ir_atype {
771 crate::sys::ipadm_addr_type_t_IPADM_ADDR_NONE => "none",
772 crate::sys::ipadm_addr_type_t_IPADM_ADDR_STATIC => "static",
773 crate::sys::ipadm_addr_type_t_IPADM_ADDR_IPV6_ADDRCONF => "addrconf",
774 crate::sys::ipadm_addr_type_t_IPADM_ADDR_DHCP => "dhcp",
775 _ => "?",
776 };
777
778 Ok((
779 aobj_name.to_string(),
780 source.to_string(),
781 resp.ir_family,
782 ifname,
783 resp.ir_lnum,
784 ))
785}