libnet/
ioctl.rs

1// Copyright 2024 Oxide Computer Company
2
3use crate::ip::{self, addrobjname_to_addrobj};
4use crate::ndpd::disable_autoconf;
5use crate::sys::{
6    self, addr_family_t, dld_ioc_macaddrget_t, dld_ioc_macprop_t,
7    dld_macaddrinfo_t, mac_prop_id_t_MAC_PROP_MTU, DLDIOC_GETMACPROP,
8    DLDIOC_MACADDRGET, SIMNET_IOC_INFO, SIMNET_IOC_MODIFY,
9};
10use crate::{Error, IpInfo, IpNet, IpState, LinkFlags};
11use libc::{free, malloc};
12use libc::{
13    sockaddr_in, sockaddr_in6, sockaddr_storage, AF_INET, AF_INET6, AF_UNSPEC,
14};
15use num_enum::TryFromPrimitive;
16use rusty_doors::{door_call_slice, door_callp};
17use socket2::{Domain, Socket, Type};
18use std::collections::BTreeMap;
19use std::convert::TryFrom;
20use std::ffi::CStr;
21use std::fs::File;
22use std::mem::size_of;
23use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
24use std::os::raw::{c_char, c_int, c_void};
25use std::os::unix::io::{AsRawFd, RawFd};
26use std::ptr;
27use tracing::{debug, warn};
28
29macro_rules! ioctl {
30    ($fd:expr, $req:expr, $arg:expr) => {
31        ioctl($fd.as_raw_fd(), ($req) as i32, $arg, stringify!($req))
32    };
33}
34
35macro_rules! ioctl_ro {
36    ($fd:expr, $req:expr, $arg:expr) => {
37        ioctl_ro($fd.as_raw_fd(), ($req) as i32, $arg, stringify!($req))
38    };
39}
40
41macro_rules! ioctl_num {
42    ($fd:expr, $req:expr) => {
43        ioctl(
44            $fd.as_raw_fd(),
45            ($req) as i32,
46            ptr::null_mut::<c_void>(),
47            stringify!($req),
48        )
49    };
50    ($fd:expr, $req:expr, $arg:expr) => {
51        ioctl(
52            $fd.as_raw_fd(),
53            ($req) as i32,
54            ($arg) as usize as *mut c_void,
55            stringify!($req),
56        )
57    };
58}
59
60unsafe fn ioctl_ro<T: ?Sized>(
61    fd: RawFd,
62    cmd: i32,
63    data: *const T,
64    cmd_str: &'static str,
65) -> Result<i32, Error> {
66    ioctl(fd, cmd, data as *mut T, cmd_str)
67}
68
69#[cfg(target_os = "illumos")]
70unsafe fn ioctl<T: ?Sized>(
71    fd: RawFd,
72    cmd: i32,
73    data: *mut T,
74    cmd_str: &'static str,
75) -> Result<i32, Error> {
76    match libc::ioctl(fd, cmd, data as *mut c_void) {
77        -1 => Err(Error::Ioctl(format!(
78            "ioctl({fd}, {cmd_str}): {}",
79            std::io::Error::last_os_error(),
80        ))),
81        other => Ok(other),
82    }
83}
84
85#[cfg(not(target_os = "illumos"))]
86unsafe fn ioctl<T: ?Sized>(
87    fd: RawFd,
88    _cmd: i32,
89    _data: *mut T,
90    cmd_str: &'static str,
91) -> Result<i32, Error> {
92    // Because these ioctls are meaningless on non-illumos targets, and the
93    // argument types for the illumos ioctl() differ from other platforms, we
94    // toss an error here, assuming that anyone attempting to run this on
95    // non-illumos is mistaken about the situation.
96    Err(Error::Ioctl(format!(
97        "ioctl({fd}, {cmd_str}): illumos required"
98    )))
99}
100
101#[repr(C)]
102struct GetMacAddrIoc {
103    get: dld_ioc_macaddrget_t,
104    info: dld_macaddrinfo_t,
105}
106
107fn dld_fd() -> Result<File, Error> {
108    std::fs::OpenOptions::new()
109        .read(true)
110        .write(true)
111        .open("/dev/dld")
112        .map_err(Error::Io)
113}
114
115#[repr(C)]
116struct SimnetModifyIoc {
117    link_id: u32,
118    peer_link_id: u32,
119    flags: u32,
120}
121
122#[repr(C)]
123pub(crate) struct SimnetInfoIoc {
124    pub(crate) link_id: u32,
125    pub(crate) peer_link_id: u32,
126    pub(crate) typ: u32,
127    pub(crate) mac_len: u32,
128    pub(crate) flags: u32,
129    pub(crate) mac_addr: [u8; sys::MAXMACADDRLEN as usize],
130}
131
132pub(crate) fn get_simnet_info(link_id: u32) -> Result<SimnetInfoIoc, Error> {
133    let fd = dld_fd()?;
134
135    unsafe {
136        let mut arg = SimnetInfoIoc {
137            link_id,
138            peer_link_id: 0,
139            typ: 0,
140            mac_len: 0,
141            flags: 0,
142            mac_addr: [0; sys::MAXMACADDRLEN as usize],
143        };
144
145        ioctl!(fd, SIMNET_IOC_INFO, &mut arg)?;
146
147        Ok(arg)
148    }
149}
150
151pub(crate) fn connect_simnet_peers(
152    link_id: u32,
153    peer_link_id: u32,
154) -> Result<(), Error> {
155    let fd = dld_fd()?;
156
157    unsafe {
158        let arg = SimnetModifyIoc {
159            link_id,
160            peer_link_id,
161            flags: 0,
162        };
163
164        ioctl_ro!(fd, SIMNET_IOC_MODIFY, &arg)?;
165        Ok(())
166    }
167}
168
169#[repr(C)]
170pub(crate) struct TfportInfoIoc {
171    pub(crate) link_id: u32,
172    pub(crate) pktsrc_id: u32,
173    pub(crate) port: u16,
174    pub(crate) mac_len: u32,
175    pub(crate) mac_addr: [u8; sys::ETHERADDRL as usize],
176}
177
178pub(crate) fn get_tfport_info(link_id: u32) -> Result<TfportInfoIoc, Error> {
179    let fd = dld_fd()?;
180
181    unsafe {
182        let mut arg = TfportInfoIoc {
183            link_id,
184            pktsrc_id: 0,
185            port: 0,
186            mac_len: 0,
187            mac_addr: [0; sys::ETHERADDRL as usize],
188        };
189
190        ioctl!(fd, sys::TFPORT_IOC_INFO, &mut arg)?;
191
192        Ok(arg)
193    }
194}
195
196#[allow(dead_code)]
197#[repr(i32)]
198pub enum VnicMacAddrType {
199    Unknown = -1,
200    Fixed,
201    Random,
202    Factory,
203    Auto,
204    Primary,
205    Vrid,
206}
207
208#[allow(dead_code)]
209#[repr(i32)]
210pub enum MacPriorityLevel {
211    Low,
212    Medium,
213    High,
214    Reset,
215}
216
217#[allow(dead_code)]
218#[repr(i32)]
219pub enum MacCpuMode {
220    Invalid = 0,
221    Fanout = 1,
222    Cpus,
223}
224
225const MRP_NCPUS: usize = 256;
226
227#[repr(C)]
228pub struct MacTxIntrCPUs {
229    pub mtc_intr_cpu: [i32; MRP_NCPUS],
230    pub mtc_retargeted_cpu: [i32; MRP_NCPUS],
231}
232impl Default for MacTxIntrCPUs {
233    fn default() -> Self {
234        Self {
235            mtc_intr_cpu: [0; MRP_NCPUS],
236            mtc_retargeted_cpu: [0; MRP_NCPUS],
237        }
238    }
239}
240
241#[repr(C)]
242pub struct MacCPUsProps {
243    pub ncpus: u32,
244    pub cpus: [u32; MRP_NCPUS],
245    pub rx_fanout_cnt: u32,
246    pub rx_fanout_cpus: [u32; MRP_NCPUS],
247    pub rx_pollid: u32,
248    pub rx_workerid: u32,
249    pub rx_intr_cpu: i32,
250    pub tx_fanout_cpus: [i32; MRP_NCPUS],
251    pub tx_intr_cpus: MacTxIntrCPUs,
252    pub fanout_mode: MacCpuMode,
253}
254impl Default for MacCPUsProps {
255    fn default() -> Self {
256        Self {
257            ncpus: 0,
258            cpus: [0; MRP_NCPUS],
259            rx_fanout_cnt: 0,
260            rx_fanout_cpus: [0; MRP_NCPUS],
261            rx_pollid: 0,
262            rx_workerid: 0,
263            rx_intr_cpu: 0,
264            tx_fanout_cpus: [0; MRP_NCPUS],
265            tx_intr_cpus: MacTxIntrCPUs::default(),
266            fanout_mode: MacCpuMode::Fanout,
267        }
268    }
269}
270
271#[repr(C)]
272pub union In6Data {
273    pub parts: [u16; 8],
274    pub align: u32,
275}
276
277impl Copy for In6Data {}
278#[allow(clippy::non_canonical_clone_impl)]
279impl Clone for In6Data {
280    fn clone(&self) -> Self {
281        unsafe {
282            let mut c = In6Data { parts: [0; 8] };
283            for (i, x) in self.parts.iter().enumerate() {
284                c.parts[i] = *x;
285            }
286            c
287        }
288    }
289}
290
291#[derive(Copy, Clone)]
292#[repr(C)]
293pub struct In6Addr {
294    pub data: In6Data,
295}
296
297impl Default for In6Addr {
298    fn default() -> Self {
299        Self {
300            data: In6Data { parts: [0; 8] },
301        }
302    }
303}
304
305#[derive(Default, Copy, Clone)]
306#[repr(C)]
307pub struct MacIpaddr {
308    pub version: u32,
309    pub addr: In6Addr,
310    pub netmask: u8,
311}
312
313#[allow(dead_code)]
314#[derive(Copy, Clone)]
315#[repr(i32)]
316pub enum MacDhcpCidFrom {
317    Typed = 1,
318    Hex,
319    Str,
320}
321
322#[derive(Copy, Clone)]
323#[repr(C)]
324pub struct MacDhcpCid {
325    pub id: [u8; MPT_MAXCIDLEN],
326    pub len: u32,
327    pub form: MacDhcpCidFrom,
328}
329impl Default for MacDhcpCid {
330    fn default() -> Self {
331        Self {
332            id: [0; MPT_MAXCIDLEN],
333            len: 0,
334            form: MacDhcpCidFrom::Typed,
335        }
336    }
337}
338
339const MPT_MAXCID: usize = 32;
340const MPT_MAXIPADDR: usize = 32;
341const MPT_MAXCIDLEN: usize = 256;
342
343#[repr(C)]
344pub struct MacProtect {
345    pub types: u32,
346    pub ipaddrcnt: u32,
347    pub ipaddrs: [MacIpaddr; MPT_MAXIPADDR],
348    pub cidcnt: u32,
349    pub cids: [MacDhcpCid; MPT_MAXCID],
350}
351
352impl Default for MacProtect {
353    fn default() -> Self {
354        Self {
355            types: 0,
356            ipaddrcnt: 0,
357            ipaddrs: [MacIpaddr::default(); MPT_MAXIPADDR],
358            cidcnt: 0,
359            cids: [MacDhcpCid::default(); MPT_MAXCID],
360        }
361    }
362}
363
364#[repr(C, packed(4))]
365pub struct MacResourceProps {
366    pub mask: u32,
367    pub maxbw: u64,
368    pub priority: MacPriorityLevel,
369    pub cpus: MacCPUsProps,
370    pub protect: MacProtect,
371    pub nrxrings: u32,
372    pub ntxrings: u32,
373    pub pool: [u8; sys::MAXPATHLEN as usize],
374}
375
376impl Default for MacResourceProps {
377    fn default() -> Self {
378        Self {
379            mask: 0,
380            maxbw: 0,
381            priority: MacPriorityLevel::Low,
382            cpus: MacCPUsProps::default(),
383            protect: MacProtect::default(),
384            nrxrings: 0,
385            ntxrings: 0,
386            pool: [0; sys::MAXPATHLEN as usize],
387        }
388    }
389}
390
391#[repr(C)]
392pub struct VnicInfoIoc {
393    pub vnic_id: u32,
394    pub link_id: u32,
395    pub mac_addr_type: VnicMacAddrType,
396    pub mac_len: u32,
397    pub mac_addr: [u8; sys::MAXMACADDRLEN as usize],
398    pub mac_slot: u32,
399    pub mac_prefix_len: u32,
400    pub vid: u16,
401    pub vrid: u32,
402    pub af: u32,
403    pub force: bool,
404    pub resource_props: MacResourceProps,
405}
406
407impl Default for VnicInfoIoc {
408    fn default() -> Self {
409        Self {
410            vnic_id: 0,
411            link_id: 0,
412            mac_addr_type: VnicMacAddrType::Unknown,
413            mac_len: 0,
414            mac_addr: [0; sys::MAXMACADDRLEN as usize],
415            mac_slot: 0,
416            mac_prefix_len: 0,
417            vid: 0,
418            vrid: 0,
419            af: 0,
420            force: false,
421            resource_props: MacResourceProps::default(),
422        }
423    }
424}
425
426pub(crate) fn get_vnic_info(link_id: u32) -> Result<VnicInfoIoc, Error> {
427    let fd = dld_fd()?;
428
429    unsafe {
430        let mut arg = VnicInfoIoc {
431            vnic_id: link_id,
432            ..Default::default()
433        };
434        ioctl!(fd, sys::VNIC_IOC_INFO, &mut arg)?;
435        Ok(arg)
436    }
437}
438
439pub(crate) fn get_macaddr(linkid: u32) -> Result<[u8; 6], Error> {
440    let fd = dld_fd()?;
441
442    unsafe {
443        let mut arg = GetMacAddrIoc {
444            get: dld_ioc_macaddrget_t {
445                dig_linkid: linkid,
446                dig_count: 1,
447                dig_size: size_of::<dld_macaddrinfo_t>() as u32,
448            },
449            info: dld_macaddrinfo_t {
450                dmi_slot: 0,
451                dmi_flags: 0,
452                dmi_addrlen: 0,
453                dmi_addr: [0; 20],
454                dmi_client_name: [0; 256],
455                dma_client_linkid: 0,
456            },
457        };
458
459        ioctl!(fd, DLDIOC_MACADDRGET, &mut arg)?;
460
461        let mut res: [u8; 6] = [0; 6];
462        res[..6].clone_from_slice(&arg.info.dmi_addr[..6]);
463
464        Ok(res)
465    }
466}
467
468pub(crate) fn get_mtu(linkid: u32) -> Result<u32, Error> {
469    let fd = dld_fd()?;
470
471    let mut arg = dld_ioc_macprop_t::<u32> {
472        pr_flags: 0,
473        pr_linkid: linkid,
474        pr_num: mac_prop_id_t_MAC_PROP_MTU,
475        pr_perm_flags: 0,
476        pr_name: [0; 256],
477        pr_valsize: size_of::<u32>() as u32,
478        pr_val: 0,
479    };
480    arg.pr_name[..3].copy_from_slice(&b"mtu".map(|u| u as i8));
481
482    unsafe {
483        ioctl!(fd, DLDIOC_GETMACPROP, &mut arg)?;
484    }
485
486    Ok(arg.pr_val)
487}
488
489pub(crate) fn get_ipaddrs() -> Result<BTreeMap<String, Vec<IpInfo>>, Error> {
490    let mut result: BTreeMap<String, Vec<IpInfo>> = BTreeMap::new();
491
492    unsafe {
493        // create sockets
494
495        let s4 = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
496        let s6 = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
497
498        // get number of interfaces
499
500        let mut lifn = sys::lifnum {
501            lifn_family: AF_UNSPEC as u16,
502            lifn_flags: 0,
503            lifn_count: 0,
504        };
505
506        ioctl!(s4, sys::SIOCGLIFNUM, &mut lifn)?;
507
508        // get interfaces
509
510        let mut ifs: Vec<sys::lifreq> = Vec::new();
511        ifs.resize(lifn.lifn_count as usize, sys::lifreq::new());
512
513        let mut lifc = sys::lifconf {
514            lifc_family: AF_UNSPEC as u16,
515            lifc_flags: (sys::LIFC_NOXMIT
516                | sys::LIFC_TEMPORARY
517                | sys::LIFC_ALLZONES
518                | sys::LIFC_UNDER_IPMP) as i32,
519            lifc_len: lifn.lifn_count * size_of::<sys::lifreq>() as i32,
520            lifc_lifcu: sys::lifconf_lifcu {
521                lifcu_buf: ifs.as_mut_ptr() as *mut c_char,
522            },
523        };
524
525        ioctl!(s4, sys::SIOCGLIFCONF, &mut lifc)?;
526
527        for x in ifs.iter_mut() {
528            let info = match ipaddr_info(x, &s4, &s6) {
529                Ok(None) => continue,
530                Ok(Some(info)) => info,
531                Err(e) => {
532                    warn!("{:?}", e);
533                    continue;
534                }
535            };
536
537            match result.get_mut(&info.ifname) {
538                None => {
539                    result.insert(info.ifname.clone(), vec![info]);
540                }
541                Some(v) => v.push(info),
542            };
543        }
544    }
545
546    Ok(result)
547}
548
549pub(crate) fn ipaddr_exists(objname: impl AsRef<str>) -> Result<bool, Error> {
550    let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")?;
551
552    // get address info
553    let mut req: ip::IpmgmtAobjopArg = unsafe { std::mem::zeroed() };
554    req.cmd = ip::IpmgmtCmd::AobjName2Addrobj;
555    for (i, c) in objname.as_ref().chars().enumerate() {
556        req.objname[i] = c as c_char;
557    }
558
559    let mut response: *mut ip::IpmgmtAobjopRval = unsafe {
560        malloc(std::mem::size_of::<ip::IpmgmtAobjopRval>())
561            as *mut ip::IpmgmtAobjopRval
562    };
563
564    let respp: *mut ip::IpmgmtAobjopRval = door_callp(
565        f.as_raw_fd(),
566        req,
567        ptr::NonNull::new(&mut response).unwrap(), // null not possible
568    );
569    let resp = unsafe { *respp };
570    if resp.err != 0 {
571        if resp.err == sys::IpadmStatusT::NotFound as i32 {
572            return Ok(false);
573        }
574        //TODO cast to enum and print that way, not correct to use errno
575        //return Err(Error::Ipmgmtd(sys::err_string(resp.err)));
576        return Ok(false);
577    }
578
579    if (resp.flags & LinkFlags::Active as u32) == 0 {
580        return Ok(false);
581    }
582
583    Ok(true)
584}
585
586pub(crate) fn delete_ipaddr(objname: impl AsRef<str>) -> Result<(), Error> {
587    let ifname = objname.as_ref().split('/').collect::<Vec<&str>>()[0];
588
589    let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")?;
590
591    // get address info
592    let mut req: ip::IpmgmtAobjopArg = unsafe { std::mem::zeroed() };
593    req.cmd = ip::IpmgmtCmd::AobjName2Addrobj;
594    for (i, c) in objname.as_ref().chars().enumerate() {
595        req.objname[i] = c as c_char;
596    }
597
598    let mut response: *mut ip::IpmgmtAobjopRval = unsafe {
599        malloc(std::mem::size_of::<ip::IpmgmtAobjopRval>())
600            as *mut ip::IpmgmtAobjopRval
601    };
602
603    let respp: *mut ip::IpmgmtAobjopRval = door_callp(
604        f.as_raw_fd(),
605        req,
606        ptr::NonNull::new(&mut response).unwrap(), // null not possible
607    );
608    let resp = unsafe { *respp };
609    if resp.err != 0 {
610        return Err(Error::Ipmgmtd(sys::err_string(resp.err)));
611    }
612
613    let af = resp.family as i32;
614
615    // delete the address from kernel
616    let mut ior: sys::lifreq = unsafe { std::mem::zeroed() };
617    let mut i = 0;
618    for c in resp.ifname {
619        if c == 0 {
620            break;
621        }
622        ior.lifr_name[i] = c;
623        i += 1;
624    }
625    if resp.lnum != 0 {
626        ior.lifr_name[i] = ':' as c_char;
627        i += 1;
628        for c in resp.lnum.to_string().chars() {
629            ior.lifr_name[i] = c as c_char;
630            i += 1;
631        }
632    }
633
634    let sock = match resp.family as i32 {
635        libc::AF_INET => Socket::new(Domain::IPV4, Type::DGRAM, None)?,
636        libc::AF_INET6 => {
637            if resp.lnum == 0 {
638                match crate::ndpd::delete_addrs(ifname) {
639                    Ok(_) => {}
640                    Err(e) => println!("ndp delete addrs: {}", e),
641                };
642            }
643            Socket::new(Domain::IPV6, Type::DGRAM, None)?
644        }
645        _ => {
646            return Err(Error::BadArgument(format!(
647                "unknown address family: {}",
648                resp.family
649            )));
650        }
651    };
652
653    if resp.lnum == 0 {
654        unsafe {
655            ior.lifr_lifru.lifru_flags &= !(sys::IFF_UP as u64);
656            ioctl_ro!(sock, sys::SIOCSLIFFLAGS, &ior)?;
657
658            if af == libc::AF_INET {
659                let sin = &mut ior.lifr_lifru.lifru_addr
660                    as *mut sockaddr_storage
661                    as *mut sockaddr_in;
662                (*sin).sin_family = AF_INET as addr_family_t;
663                (*sin).sin_addr.s_addr = 0;
664            } else if af == libc::AF_INET6 {
665                let sin6 = &mut ior.lifr_lifru.lifru_addr
666                    as *mut sockaddr_storage
667                    as *mut sockaddr_in6;
668                (*sin6).sin6_family = AF_INET6 as addr_family_t;
669                (*sin6).sin6_addr.s6_addr = [0u8; 16];
670            }
671
672            ioctl_ro!(sock, sys::SIOCSLIFADDR, &ior)?;
673        }
674    } else {
675        unsafe { ioctl_ro!(sock, sys::SIOCLIFREMOVEIF, &ior)? };
676    }
677
678    let mut ia: ip::IpmgmtAddrArg = unsafe { std::mem::zeroed() };
679    ia.cmd = ip::IpmgmtCmd::ResetAddr;
680    ia.flags = sys::IPMGMT_ACTIVE;
681    ia.lnum = resp.lnum as u32;
682    for (i, c) in objname.as_ref().chars().enumerate() {
683        ia.objname[i] = c as c_char;
684    }
685
686    // delete the address from ipmgmtd
687    unsafe {
688        let mut response: *mut ip::IpmgmtRval =
689            malloc(std::mem::size_of::<ip::IpmgmtRval>())
690                as *mut ip::IpmgmtRval;
691
692        let resp: *mut ip::IpmgmtRval = door_callp(
693            f.as_raw_fd(),
694            ia,
695            ptr::NonNull::new(&mut response).unwrap(), // null not possible
696        );
697
698        if (*resp).err != 0 {
699            free(response as *mut c_void);
700            return Err(Error::Ipmgmtd(format!(
701                "reset address: {}",
702                sys::err_string((*resp).err)
703            )));
704        }
705        free(response as *mut c_void);
706    }
707
708    unplumb_for_af(ifname, af)?;
709
710    Ok(())
711}
712
713//TODO check auth?
714pub(crate) fn create_ipaddr(
715    name: impl AsRef<str>,
716    addr: IpNet,
717) -> Result<(), Error> {
718    let parts: Vec<&str> = name.as_ref().split('/').collect();
719    if parts.len() < 2 {
720        return Err(Error::BadArgument(
721            "Expected <ifname>/<addrname>".to_string(),
722        ));
723    }
724    let ifname = parts[0];
725
726    let (iff, sock) = match addr {
727        IpNet::V4(_) => {
728            let s4 = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
729            (sys::IFF_IPV4.into(), s4)
730        }
731        IpNet::V6(_) => {
732            let s6 = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
733            (sys::IFF_IPV6.into(), s6)
734        }
735    };
736
737    match is_plumbed_for_af(ifname, &sock) {
738        true => {}
739        false => {
740            plumb_for_af(ifname, iff)?;
741        }
742    };
743
744    create_ip_addr_static(ifname, name.as_ref(), addr, &sock)
745}
746
747fn is_plumbed_for_af(name: &str, sock: &Socket) -> bool {
748    let mut req = crate::sys::lifreq::new();
749    for (i, c) in name.chars().enumerate() {
750        req.lifr_name[i] = c as c_char;
751    }
752    unsafe { ioctl!(sock, sys::SIOCGLIFFLAGS, &mut req).is_ok() }
753}
754
755fn plumb_for_af(name: &str, mut ifflags: u64) -> Result<(), Error> {
756    // TODO not handling interfaces assigned to different zones correctly.
757    // TODO not handling loopback as special case like libipadm does
758
759    let ip_h = match dlpi::open(name, dlpi::sys::DLPI_NOATTACH) {
760        Ok(h) => dlpi::DropHandle(h),
761        Err(e) => {
762            return Err(Error::Ioctl(format!(
763                "DLPI open: {}: {}",
764                e,
765                sys::errno_string()
766            )));
767        }
768    };
769    let ip_fd = match ip_h.fd() {
770        Ok(fd) => fd,
771        Err(e) => {
772            return Err(Error::Ioctl(format!("DLPI IP fd: {}", e)));
773        }
774    };
775
776    // push ip module
777    let ip_mod_name = CStr::from_bytes_with_nul(sys::IP_MOD_NAME).unwrap();
778    unsafe { ioctl_ro!(ip_fd, sys::I_PUSH, ip_mod_name.as_ptr())? };
779
780    let spec = parse_ifspec(name);
781
782    // create the new interface via SIOCSLIFNAME
783    if (ifflags & (sys::IFF_IPV6 as u64)) != 0 {
784        ifflags |= sys::IFF_NOLINKLOCAL;
785    };
786    let mut req = sys::lifreq::new();
787    req.lifr_lifru.lifru_flags = ifflags;
788    req.lifr_lifru1.lifru_ppa = spec.ppa;
789
790    for (i, c) in name.chars().enumerate() {
791        req.lifr_name[i] = c as c_char;
792    }
793    unsafe { ioctl_ro!(ip_fd, sys::SIOCSLIFNAME, &req)? };
794
795    // get flags for the interface
796    unsafe { ioctl!(ip_fd, sys::SIOCGLIFFLAGS, &mut req)? };
797    ifflags = unsafe { req.lifr_lifru.lifru_flags };
798
799    let mux_fd = if (ifflags & (sys::IFF_IPV6 as u64)) != 0 {
800        File::open("/dev/udp6")?
801    } else {
802        File::open("/dev/udp")?
803    };
804
805    // pop off unwanted modules
806    loop {
807        if unsafe { ioctl_num!(mux_fd, sys::I_POP).is_err() } {
808            break;
809        }
810    }
811
812    // push on arp module
813    let arp_mod_name = CStr::from_bytes_with_nul(sys::ARP_MOD_NAME).unwrap();
814    unsafe { ioctl_ro!(mux_fd, sys::I_PUSH, arp_mod_name)? };
815
816    // check if ARP is not needed
817    if (ifflags & ((sys::IFF_NOARP | sys::IFF_IPV6) as u64)) != 0 {
818        unsafe {
819            let res = ioctl_num!(mux_fd, sys::I_PLINK, ip_fd);
820
821            return match res {
822                Ok(ip_muxid) => {
823                    // Stash the muxid to later use while unplumbing
824                    let mut req = sys::lifreq::new();
825                    for (i, c) in name.chars().enumerate() {
826                        req.lifr_name[i] = c as c_char;
827                    }
828                    req.lifr_lifru.lif_muxid[0] = ip_muxid;
829                    ioctl!(mux_fd, sys::SIOCSLIFMUXID, &mut req)?;
830
831                    Ok(disable_autoconf(name)?)
832                }
833                Err(e) => Err(e),
834            };
835        }
836    }
837
838    // open arp dlpi
839    let arp_h = match dlpi::open(name, dlpi::sys::DLPI_NOATTACH) {
840        Ok(h) => dlpi::DropHandle(h),
841        Err(e) => return Err(Error::Ioctl(format!("DLPI ARP open: {}", e))),
842    };
843
844    let arp_fd = match arp_h.fd() {
845        Ok(fd) => fd,
846        Err(e) => {
847            return Err(Error::Ioctl(format!("DLPI ARP fd: {}", e)));
848        }
849    };
850
851    unsafe { ioctl_ro!(arp_fd, sys::I_PUSH, arp_mod_name)? };
852
853    let mut req = sys::lifreq::new();
854    req.lifr_lifru.lifru_flags = ifflags;
855    req.lifr_lifru1.lifru_ppa = spec.ppa;
856    for (i, c) in name.chars().enumerate() {
857        req.lifr_name[i] = c as c_char;
858    }
859    let arg = &mut req as *mut sys::lifreq as *mut c_char;
860    str_ioctl(
861        arp_fd,
862        sys::SIOCSLIFNAME as c_int,
863        arg,
864        size_of::<sys::lifreq>() as c_int,
865    )?;
866
867    // plink IP and arp streams
868    let ip_muxid = unsafe { ioctl_num!(mux_fd, sys::I_PLINK, ip_fd)? };
869
870    unsafe {
871        let arp_muxid = match ioctl_num!(mux_fd, sys::I_PLINK, arp_fd) {
872            Ok(muxid) => muxid,
873            Err(e) => {
874                // undo the plink of IP stream
875                if let Err(e) = ioctl_num!(mux_fd, sys::I_PUNLINK, ip_muxid) {
876                    println!("punlink failed: {}", e);
877                }
878                return Err(e);
879            }
880        };
881
882        // Stash the muxids to later use while unplumbing
883        let mut req = sys::lifreq::new();
884        for (i, c) in name.chars().enumerate() {
885            req.lifr_name[i] = c as c_char;
886        }
887        req.lifr_lifru.lif_muxid[0] = ip_muxid;
888        req.lifr_lifru.lif_muxid[1] = arp_muxid;
889        ioctl!(mux_fd, sys::SIOCSLIFMUXID, &mut req)?;
890    }
891
892    Ok(())
893}
894
895fn unplumb_for_af(name: &str, af: i32) -> Result<(), Error> {
896    let mux_fd = if af == libc::AF_INET6 {
897        File::open("/dev/udp6")?
898    } else if af == libc::AF_INET {
899        File::open("/dev/udp")?
900    } else {
901        return Err(Error::BadArgument(format!(
902            "Invalid address family: {}",
903            af
904        )));
905    };
906
907    let mut req = sys::lifreq::new();
908    for (i, c) in name.chars().enumerate() {
909        req.lifr_name[i] = c as c_char;
910    }
911
912    let (ip_muxid, arp_muxid) = unsafe {
913        ioctl!(mux_fd, sys::SIOCGLIFMUXID, &mut req)?;
914        (req.lifr_lifru.lif_muxid[0], req.lifr_lifru.lif_muxid[1])
915    };
916
917    // pop off unwanted modules
918    loop {
919        if unsafe { ioctl_num!(mux_fd, sys::I_POP).is_err() } {
920            break;
921        }
922    }
923
924    // push on arp module
925    let arp_mod_name = CStr::from_bytes_with_nul(sys::ARP_MOD_NAME).unwrap();
926    unsafe { ioctl_ro!(mux_fd, sys::I_PUSH, arp_mod_name)? };
927
928    unsafe {
929        if arp_muxid != 0 && arp_muxid != -1 {
930            if let Err(e) = ioctl_num!(mux_fd, sys::I_PUNLINK, arp_muxid) {
931                println!("punlink failed: {}", e);
932            }
933        }
934        if let Err(e) = ioctl_num!(mux_fd, sys::I_PUNLINK, ip_muxid) {
935            println!("punlink failed: {}", e);
936        }
937    }
938
939    Ok(())
940}
941
942fn str_ioctl(
943    s: c_int,
944    cmd: c_int,
945    buf: *mut c_char,
946    buflen: c_int,
947) -> Result<c_int, Error> {
948    let mut ioc = sys::strioctl::new();
949    ioc.ic_cmd = cmd;
950    ioc.ic_timeout = 0;
951    ioc.ic_len = buflen;
952    ioc.ic_dp = buf;
953
954    unsafe { ioctl!(s, sys::I_STR, &mut ioc) }
955}
956
957#[allow(dead_code)]
958pub struct IfSpec {
959    pub ppa: u32,
960    pub lun: u32,
961    pub lunvalid: bool,
962    pub devnm: [u8; sys::LIFNAMSIZ],
963}
964
965/// Parse a interface specification with the following format.
966///
967/// <name>[ppa][:lun]
968///
969/// - name: name of the device
970/// - ppa: physical point of attachment (integer)
971/// - lun: logical unit numnber (integer)
972fn parse_ifspec(ifname: &str) -> IfSpec {
973    let parts: Vec<&str> = ifname.split(':').collect();
974    let (lun, lunvalid) = {
975        match parts.len() {
976            0 => (0, false),
977            1 => (0, false),
978            _ => match parts[1].parse::<u32>() {
979                Ok(i) => (i, true),
980                Err(_) => (0, false),
981            },
982        }
983    };
984    let name = parts[0].trim_end_matches(char::is_numeric);
985    let ppa = {
986        let s = &parts[0][..name.len()];
987        s.parse::<u32>().unwrap_or(0)
988    };
989
990    let mut devnm = [0u8; sys::LIFNAMSIZ];
991    for (i, c) in name.chars().enumerate() {
992        devnm[i] = c as u8;
993    }
994
995    IfSpec {
996        ppa,
997        lun,
998        lunvalid,
999        devnm,
1000    }
1001}
1002
1003/// Get information about a specific IP interface
1004pub fn get_ipaddr_info(name: &str) -> Result<IpInfo, Error> {
1005    unsafe {
1006        let (_, _, af, mut ifname, lifnum) = addrobjname_to_addrobj(name)
1007            .map_err(|e| Error::Ioctl(format!("get addrobj: {}", e)))?;
1008
1009        let s4 = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
1010        let s6 = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
1011
1012        if lifnum > 0 {
1013            ifname = format!("{}:{}", ifname, lifnum);
1014        }
1015
1016        let mut req = sys::lifreq::new();
1017        for (i, c) in ifname.chars().enumerate() {
1018            req.lifr_name[i] = c as c_char;
1019        }
1020
1021        let ss = match af as i32 {
1022            libc::AF_INET => &s4,
1023            libc::AF_INET6 => {
1024                let sin6 = &mut req.lifr_lifru.lifru_addr
1025                    as *mut sockaddr_storage
1026                    as *mut sockaddr_in6;
1027                (*sin6).sin6_family = AF_INET6 as addr_family_t;
1028                &s6
1029            }
1030            _ => return Err(Error::NotFound(name.into())),
1031        };
1032
1033        // get addr
1034        ioctl!(ss, sys::SIOCGLIFADDR, &mut req)?;
1035
1036        match ipaddr_info(&mut req, &s4, &s6) {
1037            Ok(Some(info)) => Ok(info),
1038            Ok(None) => Err(Error::NotFound(name.into())),
1039            Err(e) => Err(e),
1040        }
1041    }
1042}
1043
1044unsafe fn ipaddr_info(
1045    x: &mut sys::lifreq,
1046    s4: &Socket,
1047    s6: &Socket,
1048) -> Result<Option<IpInfo>, Error> {
1049    let _name = CStr::from_ptr(x.lifr_name.as_ptr());
1050    let name = match _name.to_str() {
1051        Ok(s) => s,
1052        Err(_) => {
1053            return Err(Error::Ioctl("interface name conversion".into()));
1054        }
1055    };
1056
1057    let sa = x.lifr_lifru.lifru_addr;
1058    let addr = match sockaddr2ipaddr(&sa) {
1059        Some(addr) => addr,
1060        None => {
1061            // this typically means that an interface is plumbed but the ip
1062            // address has been removed, which is ok.
1063            return Ok(None);
1064        }
1065    };
1066
1067    let ss = match sa.ss_family as i32 {
1068        AF_INET => &s4,
1069        AF_INET6 => &s6,
1070        _ => return Err(Error::Ioctl("unknown address family".to_string())),
1071    };
1072
1073    // get index
1074    ioctl!(ss, sys::SIOCGLIFINDEX, x as *mut sys::lifreq)?;
1075    let idx = x.lifr_lifru.lifru_index;
1076
1077    // get netmask
1078    ioctl!(ss, sys::SIOCGLIFNETMASK, x as *mut sys::lifreq)?;
1079    let _mask = x.lifr_lifru.lifru_addr;
1080    let mask = match sockaddr2ipaddr(&_mask) {
1081        Some(mask) => mask,
1082        None => {
1083            return Err(Error::Ioctl(
1084                "socaddr to ipaddr conversion".to_string(),
1085            ))
1086        }
1087    };
1088
1089    // determine state
1090
1091    ioctl!(ss, sys::SIOCGLIFFLAGS, x as *mut sys::lifreq)?;
1092    let flags = x.lifr_lifru.lifru_flags;
1093    let state = {
1094        if flags & sys::IFF_UP as u64 != 0 {
1095            IpState::OK
1096        } else if flags & sys::IFF_RUNNING as u64 != 0 {
1097            ioctl!(ss, sys::SIOCGLIFDADSTATE, x as *mut sys::lifreq)?;
1098            if x.lifr_lifru.lifru_dadstate
1099                == sys::glif_dad_state_t_DAD_IN_PROGRESS
1100            {
1101                IpState::Tentative
1102            } else {
1103                IpState::OK
1104            }
1105        } else {
1106            IpState::Inaccessible
1107        }
1108    };
1109
1110    #[allow(clippy::unnecessary_cast)]
1111    Ok(Some(IpInfo {
1112        addr,
1113        state,
1114        ifname: name.to_string(),
1115        index: idx,
1116        mask: ip_mask(mask),
1117        family: sa.ss_family as u16,
1118    }))
1119}
1120
1121pub(crate) fn enable_v6_link_local(
1122    ifname: &str,
1123    addrname: &str,
1124) -> Result<(), Error> {
1125    let objname = format!("{}/{}", ifname, addrname);
1126
1127    let sock = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
1128
1129    if !is_plumbed_for_af(ifname, &sock) {
1130        plumb_for_af(ifname, sys::IFF_IPV6.into())?;
1131    }
1132
1133    create_ip_addr_linklocal(&sock, ifname, &objname)
1134}
1135
1136pub fn create_ip_addr_linklocal(
1137    sock: &Socket,
1138    ifname: &str,
1139    objname: &str,
1140) -> Result<(), Error> {
1141    let mut req = sys::lifreq::default();
1142    for (i, c) in ifname.chars().enumerate() {
1143        req.lifr_name[i] = c as c_char;
1144    }
1145    let (lifnum, kernel_ifname) = create_logical_interface_v6(sock, &mut req)?;
1146
1147    let ll_template = [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1148
1149    unsafe {
1150        req.lifr_lifru.lifru_addr = std::mem::zeroed();
1151
1152        let sin6 = &mut req.lifr_lifru.lifru_addr as *mut sockaddr_storage
1153            as *mut sockaddr_in6;
1154
1155        // Set netmask to /10
1156        (*sin6).sin6_family = AF_INET6 as addr_family_t;
1157        (*sin6).sin6_addr.s6_addr = [
1158            0b11111111, 0b11000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1159        ];
1160        ioctl_ro!(sock, sys::SIOCSLIFNETMASK, &req)?;
1161
1162        // Set address. In the kernel SIOCSLIFADDR uses the ill_token associated
1163        // with this interface to create an EUI-64 address.
1164        (*sin6).sin6_addr.s6_addr = ll_template;
1165        ioctl_ro!(sock, sys::SIOCSLIFPREFIX, &req)?;
1166
1167        //let (kernel_ifname, lifnum) = parse_ifname(&req)?;
1168
1169        let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")?;
1170
1171        // add placeholder to ipmgmtd
1172        add_if_to_ipmgmtd(
1173            objname,
1174            ifname,
1175            AF_INET6 as u16,
1176            lifnum,
1177            &f,
1178            ip::AddrType::Ipv6Addrconf,
1179        )?;
1180
1181        // ensure interface is up
1182        let mut req: sys::lifreq = std::mem::zeroed();
1183        for (i, c) in kernel_ifname.chars().enumerate() {
1184            req.lifr_name[i] = c as c_char;
1185        }
1186        ioctl!(sock, sys::SIOCGLIFFLAGS, &mut req)?;
1187        req.lifr_lifru.lifru_flags |= sys::IFF_UP as u64;
1188        ioctl_ro!(sock, sys::SIOCSLIFFLAGS, &req)?;
1189
1190        let intfidlen = 0;
1191        let stateless = true;
1192        let stateful = false;
1193
1194        crate::ndpd::create_addrs(
1195            ifname, *sin6, intfidlen, stateless, stateful, objname,
1196        )
1197        .map_err(|e| Error::Ioctl(format!("ndp create addrs: {}", e)))?;
1198
1199        ipmgmtd_persist(objname, ifname, lifnum, None, &f)?;
1200    }
1201
1202    Ok(())
1203}
1204
1205fn create_logical_interface_v6(
1206    sock: &Socket,
1207    req: &mut sys::lifreq,
1208) -> Result<(i32, String), Error> {
1209    unsafe {
1210        // first check if the 0th logical interface has an address
1211        ioctl!(sock, sys::SIOCGLIFADDR, req)?;
1212        let sin6 = &req.lifr_lifru.lifru_addr as *const sockaddr_storage
1213            as *const sockaddr_in6;
1214
1215        // if addr is not unspecified, this logical interface is taken, create
1216        // a new one.
1217        if (*sin6).sin6_addr.s6_addr != [0u8; 16] {
1218            ioctl!(sock, sys::SIOCLIFADDIF, req)?;
1219        }
1220
1221        let (kname, lifnum) = parse_ifname(req)?;
1222        Ok((lifnum, kname.into()))
1223    }
1224}
1225
1226fn create_logical_interface_v4(
1227    sock: &Socket,
1228    req: &mut sys::lifreq,
1229) -> Result<(i32, String), Error> {
1230    unsafe {
1231        // first check if the 0th logical interface has an address
1232        ioctl!(sock, sys::SIOCGLIFADDR, req)?;
1233        let sin4 = &req.lifr_lifru.lifru_addr as *const sockaddr_storage
1234            as *const sockaddr_in;
1235
1236        // if addr is not unspecified, this logical interface is taken, create
1237        // a new one.
1238        if (*sin4).sin_addr.s_addr != 0 {
1239            ioctl!(sock, sys::SIOCLIFADDIF, req)?;
1240        }
1241
1242        let (kname, lifnum) = parse_ifname(req)?;
1243        Ok((lifnum, kname.into()))
1244    }
1245}
1246
1247fn parse_ifname(req: &sys::lifreq) -> Result<(&str, i32), Error> {
1248    let ifname =
1249        unsafe { std::ffi::CStr::from_ptr(&req.lifr_name[0]).to_str()? };
1250
1251    let parts: Vec<&str> = ifname.split(':').collect();
1252    let lifnum = match parts.len() {
1253        2 => parts[1].parse::<i32>().unwrap_or(0),
1254        _ => 0,
1255    };
1256
1257    Ok((ifname, lifnum))
1258}
1259
1260//TODO check auth?
1261pub(crate) fn create_ip_addr_static(
1262    ifname: impl AsRef<str>,
1263    objname: impl AsRef<str>,
1264    addr: IpNet,
1265    sock: &Socket,
1266) -> Result<(), Error> {
1267    unsafe {
1268        let mut req: sys::lifreq = std::mem::zeroed();
1269        for (i, c) in ifname.as_ref().chars().enumerate() {
1270            req.lifr_name[i] = c as c_char;
1271        }
1272
1273        match addr {
1274            IpNet::V6(_) => {
1275                create_logical_interface_v6(sock, &mut req)?;
1276            }
1277            IpNet::V4(_) => {
1278                create_logical_interface_v4(sock, &mut req)?;
1279            }
1280        }
1281
1282        // assign addr
1283        match addr {
1284            IpNet::V6(a) => {
1285                req.lifr_lifru.lifru_addr.ss_family = AF_INET6 as addr_family_t;
1286                let sas =
1287                    &mut req.lifr_lifru.lifru_addr as *mut sockaddr_storage;
1288
1289                let sa6 = sas as *mut sockaddr_in6;
1290                (*sa6).sin6_addr.s6_addr = a.addr().octets();
1291            }
1292            IpNet::V4(a) => {
1293                req.lifr_lifru.lifru_addr.ss_family = AF_INET as addr_family_t;
1294                let sas =
1295                    &mut req.lifr_lifru.lifru_addr as *mut sockaddr_storage;
1296                let sa4 = sas as *mut sockaddr_in;
1297                let addr: u32 = a.addr().into();
1298                (*sa4).sin_addr.s_addr = addr.to_be();
1299            }
1300        };
1301        ioctl_ro!(sock, sys::SIOCSLIFADDR, &req)?;
1302
1303        // assign netmask
1304        match addr {
1305            IpNet::V6(a) => {
1306                req.lifr_lifru.lifru_addr.ss_family = AF_INET6 as addr_family_t;
1307                let sas =
1308                    &mut req.lifr_lifru.lifru_addr as *mut sockaddr_storage;
1309
1310                let sa6 = sas as *mut sockaddr_in6;
1311                (*sa6).sin6_addr.s6_addr = a.mask_addr().octets();
1312            }
1313            IpNet::V4(a) => {
1314                req.lifr_lifru.lifru_addr.ss_family = AF_INET as addr_family_t;
1315                let sas =
1316                    &mut req.lifr_lifru.lifru_addr as *mut sockaddr_storage;
1317
1318                let sa4 = sas as *mut sockaddr_in;
1319                let addr: u32 = a.mask_addr().into();
1320                (*sa4).sin_addr.s_addr = addr.to_be();
1321            }
1322        };
1323        ioctl_ro!(sock, sys::SIOCSLIFNETMASK, &req)?;
1324
1325        // add if to ipmgmtd
1326        let (kernel_ifname, lifnum) = parse_ifname(&req)?;
1327
1328        let af = match addr {
1329            IpNet::V6(_) => AF_INET6 as u16,
1330            IpNet::V4(_) => AF_INET as u16,
1331        };
1332
1333        let f = File::open("/etc/svc/volatile/ipadm/ipmgmt_door")?;
1334
1335        add_if_to_ipmgmtd(
1336            objname.as_ref(),
1337            ifname.as_ref(),
1338            af,
1339            lifnum,
1340            &f,
1341            ip::AddrType::Static,
1342        )?;
1343
1344        // set up
1345        let mut req: sys::lifreq = std::mem::zeroed();
1346        for (i, c) in kernel_ifname.chars().enumerate() {
1347            req.lifr_name[i] = c as c_char;
1348        }
1349        req.lifr_lifru.lifru_flags |= sys::IFF_UP as u64;
1350        ioctl_ro!(sock, sys::SIOCSLIFFLAGS, &req)?;
1351
1352        // ipmgmtd persist .. kindof
1353        ipmgmtd_persist(
1354            objname.as_ref(),
1355            ifname.as_ref(),
1356            lifnum,
1357            Some(addr),
1358            &f,
1359        )?;
1360
1361        //TODO duplicate address detection
1362    };
1363
1364    Ok(())
1365}
1366
1367fn add_if_to_ipmgmtd(
1368    objname: &str,
1369    ifname: &str,
1370    af: u16,
1371    lifnum: i32,
1372    f: &File,
1373    kind: ip::AddrType,
1374) -> Result<(), Error> {
1375    // assign name
1376    let mut iaa: ip::IpmgmtAobjopArg = unsafe { std::mem::zeroed() };
1377    iaa.cmd = ip::IpmgmtCmd::AddrobjLookupAdd;
1378    for (i, c) in objname.chars().enumerate() {
1379        iaa.objname[i] = c as c_char;
1380    }
1381    for (i, c) in ifname.chars().enumerate() {
1382        iaa.ifname[i] = c as c_char;
1383    }
1384    iaa.family = af;
1385    iaa.atype = kind;
1386
1387    let mut response: *mut ip::IpmgmtRval = unsafe {
1388        malloc(std::mem::size_of::<ip::IpmgmtRval>()) as *mut ip::IpmgmtRval
1389    };
1390
1391    door_callp(
1392        f.as_raw_fd(),
1393        iaa,
1394        ptr::NonNull::new(&mut response).unwrap(), // null not possible
1395    );
1396    unsafe { free(response as *mut c_void) };
1397
1398    // set logical interface number
1399
1400    iaa = unsafe { std::mem::zeroed() };
1401    iaa.cmd = ip::IpmgmtCmd::AddrobjSetLifnum;
1402    for (i, c) in objname.chars().enumerate() {
1403        iaa.objname[i] = c as c_char;
1404    }
1405    for (i, c) in ifname.chars().enumerate() {
1406        iaa.ifname[i] = c as c_char;
1407    }
1408    iaa.lnum = lifnum;
1409    let family = af;
1410    iaa.family = family;
1411    iaa.atype = kind;
1412
1413    let mut response: *mut ip::IpmgmtRval = unsafe {
1414        malloc(std::mem::size_of::<ip::IpmgmtRval>()) as *mut ip::IpmgmtRval
1415    };
1416
1417    door_callp(
1418        f.as_raw_fd(),
1419        iaa,
1420        ptr::NonNull::new(&mut response).unwrap(), // null not possible
1421    );
1422    unsafe { free(response as *mut c_void) };
1423
1424    Ok(())
1425}
1426
1427fn ipmgmtd_persist(
1428    objname: &str,
1429    ifname: &str,
1430    lifnum: i32,
1431    addr: Option<IpNet>,
1432    f: &File,
1433) -> Result<(), Error> {
1434    let mut nvl = nvpair::NvList::new_unique_names();
1435    nvl.insert("_ifname", ifname)?;
1436    nvl.insert("_aobjname", objname)?;
1437    nvl.insert("_lifnum", &lifnum)?;
1438
1439    match addr {
1440        Some(addr) => {
1441            let ahname = addr.addr().to_string();
1442
1443            let mut addr_nvl = nvpair::NvList::new_unique_names();
1444            addr_nvl.insert("_aname", ahname.as_str())?;
1445            let addr_nvl_name = match addr {
1446                IpNet::V6(_) => "_ipv6addr",
1447                IpNet::V4(_) => "_ipv4addr",
1448            };
1449
1450            nvl.insert(addr_nvl_name, addr_nvl.as_ref())?;
1451            nvl.insert("up", "yes")?;
1452        }
1453        // XXX hack: assuming this is a v6 link local
1454        None => {
1455            let mut ll_nvl = nvpair::NvList::new_unique_names();
1456            //let ll_template: [u8;16] = [
1457            //    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1458            //ll_nvl.insert("_addr", &ll_template[..])?;
1459            let prefixlen: u32 = 0;
1460            ll_nvl.insert("prefixlen", &prefixlen)?;
1461            ll_nvl.insert("_stateless", "yes")?;
1462            ll_nvl.insert("_stateful", "no")?;
1463            nvl.insert("_intfid", ll_nvl.as_ref())?;
1464        }
1465    }
1466
1467    let nvl_c = nvl.as_mut_ptr();
1468    let mut nvl_buf: *mut c_char = std::ptr::null_mut();
1469    let mut nvl_sz: nvpair_sys::size_t = 0;
1470    let ret = unsafe {
1471        nvpair_sys::nvlist_pack(
1472            nvl_c,
1473            &mut nvl_buf,
1474            &mut nvl_sz,
1475            nvpair_sys::NV_ENCODE_NATIVE,
1476            0,
1477        )
1478    };
1479    if ret != 0 {
1480        return Err(Error::NvPair(format!("{}", ret)));
1481    }
1482
1483    let arg = ip::IpmgmtSetAddr {
1484        cmd: ip::IpmgmtCmd::SetAddr,
1485        flags: sys::IPMGMT_ACTIVE,
1486        nvlsize: nvl_sz as u32,
1487    };
1488
1489    let mut buf: Vec<c_char> = Vec::new();
1490
1491    let arg_bytes = unsafe {
1492        std::slice::from_raw_parts(
1493            (&arg as *const ip::IpmgmtSetAddr) as *const c_char,
1494            size_of::<ip::IpmgmtSetAddr>(),
1495        )
1496    };
1497    for c in arg_bytes {
1498        buf.push(*c);
1499    }
1500
1501    let nvl_bytes =
1502        unsafe { std::slice::from_raw_parts(nvl_buf, nvl_sz as usize) };
1503    for c in nvl_bytes {
1504        buf.push(*c);
1505    }
1506
1507    let resp: ip::IpmgmtRval = door_call_slice(f.as_raw_fd(), buf.as_slice());
1508    if resp.err != 0 {
1509        return Err(Error::Ipmgmtd(sys::err_string(resp.err)));
1510    }
1511
1512    Ok(())
1513}
1514
1515fn sockaddr2ipaddr(sa: &libc::sockaddr_storage) -> Option<IpAddr> {
1516    unsafe {
1517        match sa.ss_family as i32 {
1518            libc::AF_INET => {
1519                let sa4 = sa as *const sockaddr_storage as *const sockaddr_in;
1520                Some(IpAddr::V4(Ipv4Addr::from(u32::from_be(
1521                    (*sa4).sin_addr.s_addr,
1522                ))))
1523            }
1524            libc::AF_INET6 => {
1525                let sa6 = sa as *const sockaddr_storage as *const sockaddr_in6;
1526                let a6 = IpAddr::V6(Ipv6Addr::from((*sa6).sin6_addr.s6_addr));
1527                if a6.is_unspecified() {
1528                    return None;
1529                }
1530                Some(a6)
1531            }
1532            _ => None,
1533        }
1534    }
1535}
1536
1537fn ip_mask(addr: IpAddr) -> u32 {
1538    match addr {
1539        IpAddr::V4(a4) => {
1540            let i = u32::from_be_bytes(a4.octets());
1541            i.leading_ones()
1542        }
1543        IpAddr::V6(a6) => {
1544            let i = u128::from_be_bytes(a6.octets());
1545            i.leading_ones()
1546        }
1547    }
1548}
1549
1550#[repr(C)]
1551pub struct SimnetIocCreate {
1552    pub link_id: u32,
1553    pub typ: u32,
1554    pub mac_len: u32,
1555    pub flags: u32,
1556    pub mac_addr: [u8; sys::MAXMACADDRLEN as usize],
1557}
1558
1559#[allow(dead_code)]
1560#[repr(i32)]
1561pub enum VnicIocDiag {
1562    _None,
1563    MacaddrNIC,
1564    MacaddrInUse,
1565    MacaddrInvalid,
1566    MacaddrLenInvalid,
1567    MacFactorySlotInvalid,
1568    MacFactorySlotUsed,
1569    MacFactorySlotAllUsed,
1570    MacFactoryNotSup,
1571    MacPrefixInvalid,
1572    MacPrefixLenInvalid,
1573    MacMarginInvalid,
1574    NoHwRings,
1575    MacMtuInvalid,
1576}
1577
1578#[repr(C)]
1579pub struct VnicIocCreate {
1580    pub vnic_id: u32,
1581    pub link_id: u32,
1582    pub mac_addr_type: VnicMacAddrType,
1583    pub mac_len: u32,
1584    pub mac_addr: [u8; sys::MAXMACADDRLEN as usize],
1585    pub mac_prefix_len: u32,
1586    pub mac_slot: i32,
1587    pub vid: u16,
1588    pub vrid: u32,
1589    pub af: i32,
1590    pub status: u32,
1591    pub flags: u32,
1592    pub diag: VnicIocDiag,
1593    pub resource_props: MacResourceProps,
1594}
1595
1596impl Default for VnicIocCreate {
1597    fn default() -> Self {
1598        Self {
1599            vnic_id: 0,
1600            link_id: 0,
1601            mac_addr_type: VnicMacAddrType::Unknown,
1602            mac_len: 0,
1603            mac_addr: [0; sys::MAXMACADDRLEN as usize],
1604            mac_prefix_len: 0,
1605            mac_slot: -1,
1606            vid: 0,
1607            vrid: 0,
1608            af: 0,
1609            status: 0,
1610            flags: 0,
1611            diag: VnicIocDiag::_None,
1612            resource_props: MacResourceProps::default(),
1613        }
1614    }
1615}
1616
1617pub(crate) fn create_vnic(
1618    id: u32,
1619    link_id: u32,
1620    mac: Option<Vec<u8>>,
1621) -> Result<crate::LinkInfo, Error> {
1622    unsafe {
1623        let fd = dld_fd()?;
1624
1625        debug!("creating vnic with id {}", id);
1626
1627        let mut arg = VnicIocCreate {
1628            link_id,
1629            vnic_id: id,
1630            mac_addr_type: match mac {
1631                None => VnicMacAddrType::Auto,
1632                Some(_) => VnicMacAddrType::Fixed,
1633            },
1634            mac_len: match &mac {
1635                None => 0,
1636                Some(mac) => mac.len() as u32,
1637            },
1638            mac_prefix_len: match mac {
1639                None => 3,
1640                Some(_) => 0,
1641            },
1642            mac_slot: -1,
1643            vid: 0,
1644            vrid: 0,
1645            af: AF_UNSPEC,
1646            flags: 0,
1647            ..Default::default()
1648        };
1649        match mac {
1650            None => {
1651                // This seems to be the OUI prefix used for bhyve/virtio NICs
1652                // but I cannot find a documentation reference that actually
1653                // says so.
1654                arg.mac_addr[0] = 0x02;
1655                arg.mac_addr[1] = 0x08;
1656                arg.mac_addr[2] = 0x20;
1657            }
1658            Some(mac) => {
1659                if mac.len() > 20 {
1660                    return Err(Error::BadArgument("mac too long".to_string()));
1661                }
1662                for (i, x) in mac.iter().enumerate() {
1663                    arg.mac_addr[i] = *x;
1664                }
1665            }
1666        };
1667
1668        sys::clear_errno();
1669        ioctl_ro!(fd, sys::VNIC_IOC_CREATE, &arg)?;
1670
1671        crate::link::get_link(id)
1672    }
1673}
1674
1675pub(crate) fn create_simnet(
1676    id: u32,
1677    flags: crate::LinkFlags,
1678) -> Result<crate::LinkInfo, Error> {
1679    unsafe {
1680        let fd = dld_fd()?;
1681
1682        debug!("creating simnet with id {}", id);
1683
1684        let arg = SimnetIocCreate {
1685            link_id: id,
1686            typ: sys::DL_ETHER,
1687            mac_len: 0,
1688            flags: flags as u32,
1689            mac_addr: [0; sys::MAXMACADDRLEN as usize],
1690        };
1691
1692        ioctl_ro!(fd, sys::SIMNET_IOC_CREATE, &arg)?;
1693    }
1694
1695    crate::link::get_link(id)
1696}
1697
1698#[repr(C)]
1699pub struct SimnetIocDelete {
1700    link_id: u32,
1701    flags: u32,
1702}
1703
1704pub(crate) fn delete_simnet(id: u32) -> Result<(), Error> {
1705    unsafe {
1706        let fd = dld_fd()?;
1707        let arg = SimnetIocDelete {
1708            link_id: id,
1709            flags: 0,
1710        };
1711        ioctl_ro!(fd, sys::SIMNET_IOC_DELETE, &arg)?;
1712    }
1713    Ok(())
1714}
1715
1716#[repr(C)]
1717pub struct TfportIocCreate {
1718    pub link_id: u32,
1719    pub pktsrc_id: u32,
1720    pub port: u16,
1721    pub mac_len: u32,
1722    pub mac_addr: [u8; sys::ETHERADDRL as usize],
1723}
1724
1725pub(crate) fn create_tfport(
1726    link_id: u32,
1727    pktsrc_id: u32,
1728    port: u16,
1729    mac: Option<String>,
1730) -> Result<crate::LinkInfo, Error> {
1731    let (mac_len, mac_addr) = match mac {
1732        None => (0, [0; sys::ETHERADDRL as usize]),
1733        Some(mac) => {
1734            let mut mac_addr = [0; sys::ETHERADDRL as usize];
1735            let v: Vec<&str> = mac.split(':').collect();
1736
1737            if v.len() != 6 {
1738                return Err(Error::BadArgument(
1739                    "invalid mac address".to_string(),
1740                ));
1741            }
1742
1743            for (i, octet) in v.iter().enumerate() {
1744                mac_addr[i] = u8::from_str_radix(octet, 16).map_err(|_| {
1745                    Error::BadArgument("invalid mac address".to_string())
1746                })?;
1747            }
1748            (sys::ETHERADDRL, mac_addr)
1749        }
1750    };
1751
1752    unsafe {
1753        let fd = dld_fd()?;
1754        debug!("creating tfport with id {}", link_id);
1755        let arg = TfportIocCreate {
1756            link_id,
1757            pktsrc_id,
1758            port,
1759            mac_len,
1760            mac_addr,
1761        };
1762
1763        ioctl_ro!(fd, sys::TFPORT_IOC_CREATE, &arg)?;
1764    }
1765
1766    crate::link::get_link(link_id)
1767}
1768
1769#[repr(C)]
1770pub struct TfportIocDelete {
1771    link_id: u32,
1772}
1773
1774pub(crate) fn delete_tfport(link_id: u32) -> Result<(), Error> {
1775    unsafe {
1776        let fd = dld_fd()?;
1777        let arg = TfportIocDelete { link_id };
1778        ioctl_ro!(fd, sys::TFPORT_IOC_DELETE, &arg)?;
1779    }
1780    Ok(())
1781}
1782
1783#[repr(C)]
1784pub struct VnicIocDelete {
1785    link_id: u32,
1786}
1787
1788pub(crate) fn delete_vnic(id: u32) -> Result<(), Error> {
1789    unsafe {
1790        let fd = dld_fd()?;
1791        let arg = VnicIocDelete { link_id: id };
1792        ioctl_ro!(fd, sys::VNIC_IOC_DELETE, &arg)?;
1793    }
1794    Ok(())
1795}
1796
1797#[derive(Debug)]
1798pub struct Neighbor {
1799    pub state: NeighborState,
1800    pub l2_addr: Vec<i8>,
1801    pub flags: i32,
1802}
1803
1804impl Neighbor {
1805    pub fn is_router(&self) -> bool {
1806        (self.flags & sys::NDF_ISROUTER_ON) != 0
1807    }
1808    pub fn is_anycast(&self) -> bool {
1809        (self.flags & sys::NDF_ANYCAST_ON) != 0
1810    }
1811    pub fn is_proxy(&self) -> bool {
1812        (self.flags & sys::NDF_PROXY_ON) != 0
1813    }
1814    pub fn is_static(&self) -> bool {
1815        (self.flags & sys::NDF_STATIC) != 0
1816    }
1817}
1818
1819#[derive(TryFromPrimitive, Debug)]
1820#[repr(u8)]
1821pub enum NeighborState {
1822    Unchanged,
1823    Incomplete,
1824    Reachable,
1825    Stale,
1826    Delay,
1827    Probe,
1828    Unreachable,
1829}
1830
1831pub fn get_neighbor(ifname: &str, addr: Ipv6Addr) -> Result<Neighbor, Error> {
1832    let mut ior: sys::lifreq = unsafe { std::mem::zeroed() };
1833    let mut lifru_nd_req: sys::lif_nd_req = unsafe { std::mem::zeroed() };
1834    for (i, b) in ifname.bytes().enumerate() {
1835        ior.lifr_name[i] = b as i8;
1836    }
1837
1838    let sin6 = &mut lifru_nd_req.lnr_addr as *mut sockaddr_storage
1839        as *mut sockaddr_in6;
1840    unsafe {
1841        (*sin6).sin6_family = AF_INET6 as addr_family_t;
1842        (*sin6).sin6_addr.s6_addr = addr.octets();
1843    }
1844
1845    ior.lifr_lifru = sys::lifreq_ru { lifru_nd_req };
1846
1847    let sock = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
1848
1849    unsafe { ioctl!(sock, sys::SIOCLIFGETND, &mut ior)? };
1850
1851    Ok(Neighbor {
1852        state: NeighborState::try_from(unsafe {
1853            ior.lifr_lifru.lifru_nd_req.lnr_state_create
1854        })?,
1855        l2_addr: unsafe { ior.lifr_lifru.lifru_nd_req.lnr_hdw_addr.to_vec() },
1856        flags: unsafe { ior.lifr_lifru.lifru_nd_req.lnr_flags },
1857    })
1858}
1859
1860pub fn get_ifnum(ifname: &str, af: u16) -> Result<i32, Error> {
1861    let s = match af as i32 {
1862        AF_INET => Socket::new(Domain::IPV4, Type::DGRAM, None)?,
1863        AF_INET6 => Socket::new(Domain::IPV6, Type::DGRAM, None)?,
1864        _ => return Err(Error::BadArgument("unknown address family".into())),
1865    };
1866    let mut ior: sys::lifreq = unsafe { std::mem::zeroed() };
1867    for (i, b) in ifname.as_bytes().iter().enumerate() {
1868        ior.lifr_name[i] = *b as i8;
1869    }
1870    unsafe {
1871        ioctl!(s, sys::SIOCGLIFINDEX, &mut ior)?;
1872    }
1873    Ok(unsafe { ior.lifr_lifru.lifru_index })
1874}