The consumer hands the responder subsystem of proxmox-ebpf its full
state on every trigger, the interfaces to serve and all records, and
the subsystem diffs that against the kernel state.

Signed-off-by: Hannes Laimer <[email protected]>
---
 pve-rs/Cargo.toml               |  2 +
 pve-rs/Makefile                 |  1 +
 pve-rs/debian/control           |  2 +
 pve-rs/src/bindings/sdn/dhcp.rs | 81 +++++++++++++++++++++++++++++++++
 pve-rs/src/bindings/sdn/mod.rs  |  1 +
 5 files changed, 87 insertions(+)
 create mode 100644 pve-rs/src/bindings/sdn/dhcp.rs

diff --git a/pve-rs/Cargo.toml b/pve-rs/Cargo.toml
index 5ae9082..0adf00b 100644
--- a/pve-rs/Cargo.toml
+++ b/pve-rs/Cargo.toml
@@ -34,6 +34,7 @@ proxmox-apt = { version = "1.0.1", features = ["cache"] }
 proxmox-apt-api-types = "3"
 proxmox-base64 = "1"
 proxmox-config-digest = "1"
+proxmox-ebpf = { version = "0.1", features = ["dhcp"] }
 proxmox-frr = { version = "0.5.1" }
 proxmox-http = { version = "1.0.2", features = ["client-sync", "client-trait"] 
}
 proxmox-http-error = "1"
@@ -70,6 +71,7 @@ proxmox-wireguard = { version = "0.1.2" }
 # proxmox-config-digest = { path = "../../proxmox/proxmox-config-digest" }
 # proxmox-daemon = { path = "../../proxmox/proxmox-daemon" }
 # proxmox-dns-api = { path = "../../proxmox/proxmox-dns-api" }
+# proxmox-ebpf = { path = "../../proxmox-ebpf" }
 # proxmox-http-error = { path = "../../proxmox/proxmox-http-error" }
 # proxmox-http = { path = "../../proxmox/proxmox-http" }
 # proxmox-human-byte = { path = "../../proxmox/proxmox-human-byte" }
diff --git a/pve-rs/Makefile b/pve-rs/Makefile
index bb1cd2d..7609a1e 100644
--- a/pve-rs/Makefile
+++ b/pve-rs/Makefile
@@ -32,6 +32,7 @@ PERLMOD_PACKAGES := \
          PVE::RS::OpenId \
          PVE::RS::ResourceScheduling::Static \
          PVE::RS::ResourceScheduling::Dynamic \
+         PVE::RS::SDN::Dhcp \
          PVE::RS::SDN::Fabrics \
          PVE::RS::SDN::PrefixLists \
          PVE::RS::SDN::RouteMaps \
diff --git a/pve-rs/debian/control b/pve-rs/debian/control
index 70367dc..521d966 100644
--- a/pve-rs/debian/control
+++ b/pve-rs/debian/control
@@ -20,6 +20,8 @@ Build-Depends: debhelper-compat (= 13),
                librust-proxmox-apt-api-types-3+default-dev (>= 3.0.0-~~),
                librust-proxmox-base64-1+default-dev,
                librust-proxmox-config-digest-1+default-dev,
+               librust-proxmox-ebpf-0.1+default-dev,
+               librust-proxmox-ebpf-0.1+dhcp-dev,
                librust-proxmox-frr-0.5+default-dev (>= 0.5.1-~~),
                librust-proxmox-http-1+client-sync-dev (>= 1.0.2-~~),
                librust-proxmox-http-1+client-trait-dev (>= 1.0.2-~~),
diff --git a/pve-rs/src/bindings/sdn/dhcp.rs b/pve-rs/src/bindings/sdn/dhcp.rs
new file mode 100644
index 0000000..510e115
--- /dev/null
+++ b/pve-rs/src/bindings/sdn/dhcp.rs
@@ -0,0 +1,81 @@
+#[perlmod::package(name = "PVE::RS::SDN::Dhcp", lib = "pve_rs")]
+pub mod pve_rs_sdn_dhcp {
+    //! The `PVE::RS::SDN::Dhcp` package.
+    //!
+    //! Bindings for the per-tap eBPF DHCP responder. Records handed in here 
are
+    //! answered directly from the kernel.
+
+    use std::net::Ipv4Addr;
+
+    use anyhow::{Context, Error, bail};
+    use serde::Deserialize;
+
+    use proxmox_ebpf::dhcp::{DhcpSubsystem, Record};
+
+    /// One MAC's DHCP answer, the address and every option the reply carries.
+    #[derive(Deserialize)]
+    pub struct DhcpRecord {
+        mac: String,
+        ip: Ipv4Addr,
+        prefixlen: u8,
+        server_id: Ipv4Addr,
+        lease: u32,
+        router: Option<Ipv4Addr>,
+        dns: Option<Ipv4Addr>,
+        mtu: Option<u16>,
+    }
+
+    fn parse_mac(s: &str) -> Result<[u8; 6], Error> {
+        let mut addr = [0u8; 6];
+        let mut n = 0;
+        for part in s.split(':') {
+            if n >= 6 || part.len() != 2 {
+                bail!("invalid MAC {s:?}");
+            }
+            addr[n] = u8::from_str_radix(part, 16).with_context(|| 
format!("invalid MAC {s:?}"))?;
+            n += 1;
+        }
+        if n != 6 {
+            bail!("invalid MAC {s:?}");
+        }
+        Ok(addr)
+    }
+
+    fn to_records(records: Vec<DhcpRecord>) -> Result<Vec<Record>, Error> {
+        records
+            .into_iter()
+            .map(|r| {
+                Ok(Record {
+                    mac: parse_mac(&r.mac)?,
+                    ip: r.ip,
+                    prefixlen: r.prefixlen,
+                    server_id: r.server_id,
+                    lease: r.lease,
+                    router: r.router,
+                    dns: r.dns,
+                    mtu: r.mtu,
+                })
+            })
+            .collect()
+    }
+
+    /// The full pass. Make the responder programs current, attach them to 
exactly the given
+    /// interfaces and make the record map hold exactly the given records.
+    #[export]
+    pub fn apply(ifaces: Vec<String>, records: Vec<DhcpRecord>) -> Result<(), 
Error> {
+        let ifaces: Vec<&str> = ifaces.iter().map(String::as_str).collect();
+        DhcpSubsystem::new().apply(&ifaces, &to_records(records)?)
+    }
+
+    /// Attach the responder to a guest interface.
+    #[export]
+    pub fn attach(iface: &str) -> Result<(), Error> {
+        DhcpSubsystem::new().attach(iface)
+    }
+
+    /// Detach the responder everywhere and drop its pinned state.
+    #[export]
+    pub fn clear() -> Result<(), Error> {
+        DhcpSubsystem::new().clear()
+    }
+}
diff --git a/pve-rs/src/bindings/sdn/mod.rs b/pve-rs/src/bindings/sdn/mod.rs
index dcae046..4b99b8f 100644
--- a/pve-rs/src/bindings/sdn/mod.rs
+++ b/pve-rs/src/bindings/sdn/mod.rs
@@ -1,3 +1,4 @@
+pub(crate) mod dhcp;
 pub(crate) mod fabrics;
 pub(crate) mod prefix_lists;
 pub(crate) mod route_maps;
-- 
2.47.3




Reply via email to