Skip to main content

zlayer_overlay/
networkd.rs

1//! Keep the host's network managers from touching overlay interfaces.
2//!
3//! `systemd-networkd` (and `NetworkManager`) with a permissive default match
4//! will try to MANAGE a freshly-created overlay link (`zl-*`, `veth-*`) — bring it
5//! up, run DHCP / IPv6 autoconf, and (observed on a CI runner) hit the
6//! 3-minute systemd-networkd watchdog and `SIGABRT` while processing a `zl-*`
7//! `Link UP`. overlayd configures these links itself via netlink, so we drop a
8//! tiny "Unmanaged" config for each manager that is present.
9//!
10//! Best-effort + idempotent: a manager's drop-in is only written when that
11//! manager's runtime dir exists, every error is logged (never fatal), and the
12//! manager is reloaded so it re-evaluates already-created links. Root-mode only
13//! (rootless overlay creates no host `zl-*` links — `pasta` handles egress);
14//! a pure no-op on non-Linux.
15
16#[cfg(target_os = "linux")]
17mod imp {
18    use std::path::Path;
19
20    pub(super) const NETWORKD_DROPIN: &str = "/run/systemd/network/10-zlayer-unmanaged.network";
21    pub(super) const NM_DROPIN: &str = "/run/NetworkManager/conf.d/10-zlayer-unmanaged.conf";
22
23    /// systemd-networkd `.network` drop-in: match the overlay link name globs
24    /// and mark them unmanaged so networkd never brings them up / runs the
25    /// watchdog against them.
26    pub(super) const NETWORKD_BODY: &str = "# Managed by ZLayer overlayd — do not edit.\n\
27# Overlay links are configured by overlayd via netlink; keep systemd-networkd\n\
28# from managing them (it otherwise hits its watchdog on a zl-* Link UP).\n\
29[Match]\n\
30Name=zl-* veth-*\n\
31\n\
32[Link]\n\
33Unmanaged=yes\n";
34
35    /// `NetworkManager` keyfile drop-in marking the same interface-name globs
36    /// unmanaged.
37    pub(super) const NM_BODY: &str = "# Managed by ZLayer overlayd — do not edit.\n\
38[keyfile]\n\
39unmanaged-devices=interface-name:zl-*;interface-name:veth-*\n";
40
41    pub(super) fn mark() {
42        // systemd-networkd: present whenever `/run/systemd` exists (systemd
43        // host). Create the runtime config dir if needed, write, reload.
44        if Path::new("/run/systemd").is_dir() {
45            if let Err(e) = std::fs::create_dir_all("/run/systemd/network") {
46                tracing::warn!(error = %e, "could not create /run/systemd/network for unmanaged drop-in");
47            } else if let Err(e) = std::fs::write(NETWORKD_DROPIN, NETWORKD_BODY) {
48                tracing::warn!(error = %e, path = NETWORKD_DROPIN, "failed to write systemd-networkd unmanaged drop-in");
49            } else {
50                let _ = std::process::Command::new("networkctl")
51                    .arg("reload")
52                    .status();
53                tracing::info!(
54                    path = NETWORKD_DROPIN,
55                    "marked overlay interfaces Unmanaged for systemd-networkd"
56                );
57            }
58        }
59        // NetworkManager: only when it is actually running (`/run/NetworkManager`
60        // exists). Create conf.d if missing, write, reload.
61        if Path::new("/run/NetworkManager").is_dir() {
62            if let Err(e) = std::fs::create_dir_all("/run/NetworkManager/conf.d") {
63                tracing::warn!(error = %e, "could not create /run/NetworkManager/conf.d for unmanaged drop-in");
64            } else if let Err(e) = std::fs::write(NM_DROPIN, NM_BODY) {
65                tracing::warn!(error = %e, path = NM_DROPIN, "failed to write NetworkManager unmanaged drop-in");
66            } else {
67                let _ = std::process::Command::new("nmcli")
68                    .args(["general", "reload"])
69                    .status();
70                tracing::info!(
71                    path = NM_DROPIN,
72                    "marked overlay interfaces unmanaged for NetworkManager"
73                );
74            }
75        }
76    }
77
78    pub(super) fn unmark() {
79        // Remove each drop-in if present and reload the manager. Idempotent:
80        // a missing file is success.
81        let mut reload_networkd = false;
82        match std::fs::remove_file(NETWORKD_DROPIN) {
83            Ok(()) => reload_networkd = true,
84            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
85            Err(e) => {
86                tracing::warn!(error = %e, path = NETWORKD_DROPIN, "failed to remove networkd unmanaged drop-in");
87            }
88        }
89        if reload_networkd {
90            let _ = std::process::Command::new("networkctl")
91                .arg("reload")
92                .status();
93        }
94
95        let mut reload_nm = false;
96        match std::fs::remove_file(NM_DROPIN) {
97            Ok(()) => reload_nm = true,
98            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
99            Err(e) => {
100                tracing::warn!(error = %e, path = NM_DROPIN, "failed to remove NetworkManager unmanaged drop-in");
101            }
102        }
103        if reload_nm {
104            let _ = std::process::Command::new("nmcli")
105                .args(["general", "reload"])
106                .status();
107        }
108    }
109}
110
111/// Install drop-ins telling `systemd-networkd` and `NetworkManager` to leave
112/// `zl-*` / `veth-*` overlay interfaces unmanaged. Best-effort, idempotent,
113/// root-mode only. No-op on non-Linux.
114pub fn mark_overlay_interfaces_unmanaged() {
115    #[cfg(target_os = "linux")]
116    imp::mark();
117}
118
119/// Remove the unmanaged drop-ins (clean reversion on overlay teardown).
120/// Best-effort, idempotent. No-op on non-Linux.
121pub fn unmark_overlay_interfaces_unmanaged() {
122    #[cfg(target_os = "linux")]
123    imp::unmark();
124}
125
126#[cfg(all(test, target_os = "linux"))]
127mod tests {
128    use super::imp::{NETWORKD_BODY, NM_BODY};
129
130    #[test]
131    fn networkd_dropin_marks_overlay_links_unmanaged() {
132        assert!(NETWORKD_BODY.contains("[Match]"));
133        assert!(
134            NETWORKD_BODY.contains("Name=zl-* veth-*"),
135            "must match both zl-* and veth-* overlay links"
136        );
137        assert!(NETWORKD_BODY.contains("[Link]"));
138        assert!(
139            NETWORKD_BODY.contains("Unmanaged=yes"),
140            "must mark the matched links Unmanaged"
141        );
142    }
143
144    #[test]
145    fn networkmanager_dropin_marks_overlay_links_unmanaged() {
146        assert!(NM_BODY.contains("[keyfile]"));
147        assert!(
148            NM_BODY.contains("unmanaged-devices=interface-name:zl-*;interface-name:veth-*"),
149            "must mark zl-* and veth-* unmanaged for NetworkManager"
150        );
151    }
152}