libnet/
link.rs

1// Copyright 2021 Oxide Computer Company
2
3use crate::sys;
4use crate::{Error, LinkClass, LinkFlags, LinkInfo};
5use libc::ENOENT;
6use rusty_doors::door_call;
7use std::fs::File;
8use std::os::unix::io::AsRawFd;
9use std::str;
10use tracing::{debug, warn};
11
12const DATALINK_ANY_MEDIATYPE: u64 = 0x01 << 32;
13
14#[derive(Debug)]
15#[repr(u32)]
16pub enum DlmgmtCmd {
17    DLScreate = 1,
18    DLSgetattr = 2,
19    DLSdestroy = 3,
20    GetName = 4,
21    GetLinkId = 5,
22    GetNext = 6,
23    DLSupdate = 7,
24    LinkPropInit = 8,
25    SetZoneId = 9,
26    CreateLinkId = 128,
27    DestroyLinkId = 129,
28    RemapLinkId = 130,
29    CreateConf = 131,
30    OpenConf = 132,
31    WriteConf = 133,
32    UpLinkId = 134,
33    SetAttr = 135,
34    UnsetAttr = 136,
35    RemoveConf = 137,
36    DestroyConf = 138,
37    GetAttr = 139,
38    GetConfSnapshot = 140,
39    ZoneBoot = 141,
40    ZoneHalt = 142,
41}
42
43#[derive(Debug)]
44#[repr(C)]
45pub struct DlmgmtGetNext {
46    pub cmd: u32,
47    pub linkid: u32,
48    pub class: LinkClass,
49    pub flags: LinkFlags,
50    pub media: u64,
51}
52
53impl Default for DlmgmtGetNext {
54    fn default() -> Self {
55        DlmgmtGetNext {
56            cmd: DlmgmtCmd::GetNext as u32,
57            linkid: 0,
58            class: LinkClass::All,
59            flags: LinkFlags::ActivePersistent,
60            media: DATALINK_ANY_MEDIATYPE,
61        }
62    }
63}
64
65#[derive(Debug)]
66#[repr(C)]
67pub struct DlmgmtGetName {
68    pub cmd: u32,
69    pub linkid: u32,
70}
71
72impl Default for DlmgmtGetName {
73    fn default() -> Self {
74        DlmgmtGetName {
75            cmd: DlmgmtCmd::GetName as u32,
76            linkid: 0,
77        }
78    }
79}
80
81#[derive(Debug)]
82#[repr(C)]
83pub struct DlmgmtNameRetval {
84    pub err: u32,
85    pub link: [u8; 32],
86    pub class: LinkClass,
87    pub media: u32,
88    pub flags: LinkFlags,
89}
90
91impl Default for DlmgmtNameRetval {
92    fn default() -> Self {
93        DlmgmtNameRetval {
94            err: 0,
95            link: [0; 32],
96            class: LinkClass::Phys,
97            media: 0,
98            flags: LinkFlags::ActivePersistent,
99        }
100    }
101}
102
103#[derive(Debug)]
104#[repr(C)]
105pub struct DlmgmtLinkRetval {
106    pub err: u32,
107    pub linkid: u32,
108    pub flags: LinkFlags,
109    pub class: LinkClass,
110    pub media: u32,
111    pub padding: u32,
112}
113
114impl Default for DlmgmtLinkRetval {
115    fn default() -> Self {
116        DlmgmtLinkRetval {
117            err: 0,
118            linkid: 0,
119            flags: LinkFlags::ActivePersistent,
120            class: LinkClass::All,
121            media: 0,
122            padding: 0,
123        }
124    }
125}
126
127pub(crate) fn get_links() -> Result<Vec<LinkInfo>, Error> {
128    let mut result = Vec::new();
129    let mut linkid = 0;
130    let f = dlmgmt_door_fd()?;
131
132    loop {
133        let request = DlmgmtGetNext {
134            linkid,
135            ..Default::default()
136        };
137        let response: DlmgmtLinkRetval = door_call(f.as_raw_fd(), request);
138
139        if response.linkid == 0 || response.err == (ENOENT as u32) {
140            break;
141        }
142        if response.err != 0 {
143            warn!("Door error: {:#?}", response);
144            break;
145        }
146
147        linkid = response.linkid;
148
149        match get_link(response.linkid) {
150            Ok(lnk) => result.push(lnk),
151            Err(e) => warn!("{}", e),
152        };
153    }
154
155    Ok(result)
156}
157
158pub(crate) fn get_link(id: u32) -> Result<LinkInfo, Error> {
159    let f = dlmgmt_door_fd()?;
160
161    let name_request = DlmgmtGetName {
162        cmd: DlmgmtCmd::GetName as u32,
163        linkid: id,
164    };
165
166    let response: DlmgmtNameRetval = door_call(f.as_raw_fd(), name_request);
167    let name: &str = match response.err {
168        0 => {
169            let end = response
170                .link
171                .iter()
172                .position(|&c| c == b'\0')
173                .expect("bad link name");
174            str::from_utf8(&response.link[0..end])?
175        }
176        _ => return Err(Error::NotFound(format!("link-id {} not found", id))),
177    };
178
179    let link_state = match crate::kstat::get_linkstate(name) {
180        Ok(state) => match state {
181            sys::link_state_t_LINK_STATE_UP => crate::LinkState::Up,
182            sys::link_state_t_LINK_STATE_DOWN => crate::LinkState::Down,
183            _ => crate::LinkState::Unknown,
184        },
185        Err(e) => {
186            warn!("error fetching link state on linkid {}: {}", id, e);
187            crate::LinkState::Unknown
188        }
189    };
190
191    let mac = match crate::ioctl::get_macaddr(id) {
192        Ok(mac) => mac,
193        Err(e) => {
194            warn!("error fetching mac address on linkid {}: {}", id, e);
195            [0u8; 6]
196        }
197    };
198
199    let mtu = match crate::ioctl::get_mtu(id) {
200        Ok(mtu) => Some(mtu),
201        Err(e) => {
202            warn!("error fetching mtu on linkid {}: {}", id, e);
203            None
204        }
205    };
206
207    let over = match response.class {
208        LinkClass::Simnet => match crate::ioctl::get_simnet_info(id) {
209            Ok(info) => info.peer_link_id,
210            Err(_) => {
211                warn!("could not get vnic info for {} ({})", name, id);
212                0
213            }
214        },
215        LinkClass::Tfport => match crate::ioctl::get_tfport_info(id) {
216            Ok(info) => info.pktsrc_id,
217            Err(_) => {
218                warn!("could not get tfport info for {} ({})", name, id);
219                0
220            }
221        },
222        LinkClass::Vnic => match crate::ioctl::get_vnic_info(id) {
223            Ok(info) => info.link_id,
224            Err(_) => {
225                warn!("could not get vnic info for {} ({})", name, id);
226                0
227            }
228        },
229        _ => 0,
230    };
231
232    Ok(LinkInfo {
233        id,
234        mac,
235        mtu,
236        over,
237        name: name.to_string(),
238        flags: response.flags,
239        class: response.class,
240        state: link_state,
241    })
242}
243
244#[repr(C)]
245pub enum DlmgmtDoorAttrType {
246    Str,
247    Boolean,
248    Uint64,
249}
250
251#[repr(C)]
252struct DlmgmtDoorWriteConf {
253    cmd: DlmgmtCmd,
254    conf_id: u32,
255}
256
257#[repr(C)]
258#[derive(Default)]
259struct DlmgmtRetval {
260    err: u32,
261}
262
263#[repr(C)]
264struct DlmgmtDoorOpenConf {
265    cmd: DlmgmtCmd,
266    linkid: u32,
267}
268
269#[repr(C)]
270#[derive(Default)]
271struct DlmgmtOpenConfRetval {
272    err: u32,
273    conf_id: u32,
274}
275
276const MAXLINKATTRLEN: usize = 32;
277const MAXLINKATTRVALLEN: usize = 1024;
278
279#[repr(C)]
280struct DlmgmtDoorSetAttr {
281    cmd: DlmgmtCmd,
282    conf_id: u32,
283    attr: [u8; MAXLINKATTRLEN],
284    attr_sz: u32,
285    typ: DlmgmtDoorAttrType,
286    val: [u8; MAXLINKATTRVALLEN],
287}
288
289#[repr(C)]
290struct DlmgmtDoorUnsetAttr {
291    cmd: DlmgmtCmd,
292    conf_id: u32,
293    attr: [u8; MAXLINKATTRLEN],
294}
295
296#[repr(C)]
297struct DlmgmtDoorDestroyConf {
298    cmd: DlmgmtCmd,
299    conf_id: u32,
300}
301
302//TODO this is coming back once i get around to persistent confi
303#[allow(dead_code)]
304pub(crate) fn connect_simnet_peers(
305    link_id_a: u32,
306    link_id_b: u32,
307) -> Result<(), Error> {
308    let peer_info = get_link(link_id_b)?;
309
310    let key = "simnetpeer";
311
312    let f = dlmgmt_door_fd()?;
313
314    // open configuration
315    let open_request = DlmgmtDoorOpenConf {
316        cmd: DlmgmtCmd::OpenConf,
317        linkid: link_id_a,
318    };
319
320    let open_response: DlmgmtOpenConfRetval =
321        door_call(f.as_raw_fd(), open_request);
322    if open_response.err != 0 {
323        return Err(Error::Dlmgmtd(format!(
324            "openconf failed: {}",
325            open_response.err
326        )));
327    }
328    if open_response.conf_id == 0 {
329        return Err(Error::Dlmgmtd("open conf returned confid 0".into()));
330    }
331    let conf_id = open_response.conf_id;
332    debug!("got confid {}", conf_id);
333
334    // clear previous value
335    let mut clear_request = DlmgmtDoorUnsetAttr {
336        cmd: DlmgmtCmd::UnsetAttr,
337        conf_id,
338        attr: [0; MAXLINKATTRLEN],
339    };
340    for (i, c) in key.chars().enumerate() {
341        clear_request.attr[i] = c as u8;
342    }
343    let clear_response: DlmgmtRetval = door_call(f.as_raw_fd(), clear_request);
344    if clear_response.err != 0 {
345        close_conf(f.as_raw_fd(), conf_id)?;
346        return Err(Error::Dlmgmtd(format!(
347            "clear conf failed: {}",
348            open_response.err
349        )));
350    }
351
352    debug!("setting peer={} for link id {}", peer_info.name, link_id_a);
353
354    // set attribute
355    let mut set_request = DlmgmtDoorSetAttr {
356        cmd: DlmgmtCmd::SetAttr,
357        conf_id,
358        attr: [0; MAXLINKATTRLEN],
359        attr_sz: (peer_info.name.len() + 1) as u32,
360        typ: DlmgmtDoorAttrType::Str,
361        val: [0; MAXLINKATTRVALLEN],
362    };
363    for (i, c) in key.chars().enumerate() {
364        set_request.attr[i] = c as u8;
365    }
366    for (i, b) in peer_info.name.chars().enumerate() {
367        set_request.val[i] = b as u8;
368    }
369    let set_response: DlmgmtRetval = door_call(f.as_raw_fd(), set_request);
370    if set_response.err != 0 {
371        close_conf(f.as_raw_fd(), conf_id)?;
372        return Err(Error::Dlmgmtd(format!(
373            "set conf attr failed: {}",
374            set_response.err
375        )));
376    }
377
378    // write new config
379    let write_request = DlmgmtDoorWriteConf {
380        cmd: DlmgmtCmd::WriteConf,
381        conf_id,
382    };
383    let write_response: DlmgmtRetval = door_call(f.as_raw_fd(), write_request);
384    if write_response.err != 0 {
385        close_conf(f.as_raw_fd(), conf_id)?;
386        return Err(Error::Dlmgmtd(format!(
387            "write conf failed: {}",
388            write_response.err
389        )));
390    }
391
392    // close conf
393    close_conf(f.as_raw_fd(), conf_id)?;
394
395    Ok(())
396}
397
398fn close_conf(fd: i32, conf_id: u32) -> Result<(), Error> {
399    let close_request = DlmgmtDoorDestroyConf {
400        cmd: DlmgmtCmd::DestroyConf,
401        conf_id,
402    };
403    let close_response: DlmgmtRetval = door_call(fd, close_request);
404    if close_response.err != 0 {
405        return Err(Error::Dlmgmtd(format!(
406            "close conf failed: {}",
407            close_response.err
408        )));
409    }
410    Ok(())
411}
412
413pub(crate) fn create_simnet_link(
414    name: &str,
415    flags: LinkFlags,
416) -> Result<LinkInfo, Error> {
417    let id = crate::link::create_link_id(name, LinkClass::Simnet, flags)?;
418    let link_info = match crate::ioctl::create_simnet(id, flags) {
419        Ok(l) => Ok(l),
420        Err(e) => {
421            let _ = delete_link_id(id, flags);
422            Err(e)
423        }
424    }?;
425    if (flags as u32 & LinkFlags::Persistent as u32) != 0 {
426        //TODO
427        //save_simnet(name, flags)?;
428    }
429
430    Ok(link_info)
431}
432
433pub(crate) fn create_tfport_link(
434    name: &str,
435    over: &str,
436    port: u16,
437    mac: Option<String>,
438    flags: LinkFlags,
439) -> Result<LinkInfo, Error> {
440    let over_id = linkname_to_id(over)?;
441    let link_id = crate::link::create_link_id(name, LinkClass::Tfport, flags)?;
442    match crate::ioctl::create_tfport(link_id, over_id, port, mac) {
443        Ok(l) => Ok(l),
444        Err(e) => {
445            let _ = delete_link_id(link_id, flags);
446            Err(e)
447        }
448    }
449}
450
451pub fn create_vnic_link(
452    name: &str,
453    link: u32,
454    mac: Option<Vec<u8>>,
455    flags: LinkFlags,
456) -> Result<LinkInfo, Error> {
457    let id = crate::link::create_link_id(name, LinkClass::Vnic, flags)?;
458    let link_info = match crate::ioctl::create_vnic(id, link, mac) {
459        Ok(l) => Ok(l),
460        Err(e) => {
461            let _ = delete_link_id(id, flags);
462            Err(e)
463        }
464    }?;
465    if (flags as u32 & LinkFlags::Persistent as u32) != 0 {
466        //TODO
467        //save_simnet(name, flags)?;
468    }
469
470    Ok(link_info)
471}
472
473#[repr(C)]
474pub struct DlmgmtGetLinkId {
475    pub cmd: DlmgmtCmd,
476    pub name: [u8; crate::sys::MAXLINKNAMELEN as usize],
477}
478
479pub fn linkname_to_id(name: &str) -> Result<u32, Error> {
480    let mut request = DlmgmtGetLinkId {
481        cmd: DlmgmtCmd::GetLinkId,
482        name: [0; crate::sys::MAXLINKNAMELEN as usize],
483    };
484    for (i, c) in name.chars().enumerate() {
485        request.name[i] = c as u8;
486    }
487
488    let f = dlmgmt_door_fd()?;
489
490    let response: DlmgmtLinkRetval = door_call(f.as_raw_fd(), request);
491    if response.err == (ENOENT as u32) || response.linkid == 0 {
492        return Err(Error::NotFound(format!("link {} not found", name)));
493    }
494    if response.err != 0 {
495        return Err(Error::Dlmgmtd(format!("get linkid: {}", response.err)));
496    }
497
498    Ok(response.linkid)
499}
500
501/* TODO
502pub(crate) fn save_simnet(_name: &String, _flags: LinkFlags) -> Result<(), Error> {
503    Err(Error::NotImplemented)
504}
505*/
506
507pub(crate) fn delete_link(id: u32, flags: LinkFlags) -> Result<(), Error> {
508    // delete the active link
509    let link = match get_link(id) {
510        Err(Error::NotFound(_)) => return Ok(()),
511        Err(e) => return Err(e),
512        Ok(link) => link,
513    };
514
515    if let Err(err) = match link.class {
516        LinkClass::Simnet => crate::ioctl::delete_simnet(id),
517        LinkClass::Vnic => crate::ioctl::delete_vnic(id),
518        LinkClass::Tfport => crate::ioctl::delete_tfport(id),
519        _ => Err(Error::NotImplemented),
520    } {
521        warn!("class-specific delete error: {}", err);
522        return Err(err);
523    }
524
525    if let Err(e) = delete_link_id(id, flags) {
526        warn!("failed to delete link: {}", e);
527        return Err(e);
528    }
529
530    // TODO delete the persistent link
531    Ok(())
532}
533
534#[derive(Debug)]
535#[repr(C)]
536pub enum Bool {
537    False,
538    True,
539}
540
541#[derive(Debug)]
542#[repr(C)]
543pub struct DlmgmtDoorCreateId {
544    pub cmd: u32,
545    pub link: [u8; crate::sys::MAXLINKNAMELEN as usize],
546    pub class: u32,
547    pub media: u32,
548    pub prefix: Bool,
549    pub flags: u32,
550}
551
552fn dlmgmt_door_fd() -> Result<File, Error> {
553    File::open("/etc/svc/volatile/dladm/dlmgmt_door").map_err(Error::Io)
554}
555
556pub fn create_link_id(
557    name: &str,
558    class: LinkClass,
559    flags: LinkFlags,
560) -> Result<u32, Error> {
561    let f = dlmgmt_door_fd()?;
562
563    let mut link = [0u8; crate::sys::MAXLINKNAMELEN as usize];
564    if name.len() >= crate::sys::MAXLINKNAMELEN as usize {
565        return Err(Error::BadArgument(format!(
566            "link name must be less than {} characters",
567            crate::sys::MAXLINKNAMELEN,
568        )));
569    }
570    for (i, c) in name.chars().enumerate() {
571        link[i] = c as u8;
572    }
573
574    let request = DlmgmtDoorCreateId {
575        cmd: crate::link::DlmgmtCmd::CreateLinkId as u32,
576        link,
577        class: class as u32,
578        media: crate::sys::DL_ETHER,
579        prefix: Bool::False,
580        flags: flags as u32,
581    };
582
583    let response: DlmgmtLinkRetval = door_call(f.as_raw_fd(), request);
584    if response.linkid == 0 || response.err != 0 {
585        return Err(Error::Dlmgmtd(format!(
586            "link id creation failed: {}",
587            response.err
588        )));
589    }
590
591    Ok(response.linkid)
592}
593
594#[repr(C)]
595struct DlmgmtDoorDestroyId {
596    cmd: u32,
597    id: u32,
598    flags: u32,
599}
600
601pub fn delete_link_id(id: u32, flags: LinkFlags) -> Result<(), Error> {
602    let f = dlmgmt_door_fd()?;
603
604    let request = DlmgmtDoorDestroyId {
605        cmd: crate::link::DlmgmtCmd::DestroyLinkId as u32,
606        id,
607        flags: flags as u32,
608    };
609
610    let response: DlmgmtLinkRetval = door_call(f.as_raw_fd(), request);
611    if response.err != 0 {
612        return Err(Error::Dlmgmtd(format!(
613            "link id delete failed: {}",
614            response.err
615        )));
616    }
617
618    Ok(())
619}