From b4aadacd2eefe73e85c5c80eb70ed8ab155c347f Mon Sep 17 00:00:00 2001 From: Aditya R Date: Wed, 6 Dec 2023 12:08:04 +0530 Subject: [PATCH] ipam, network: implement IPAM management This PR intends to add IPAM Management code to netavark. Currently PR is in progress. [NO NEW TESTS NEEDED] Signed-off-by: Aditya R --- Cargo.lock | 101 +++++++++++++++++++++++++ Cargo.toml | 1 + src/ipam/mod.rs | 2 + src/ipam/store.rs | 189 ++++++++++++++++++++++++++++++++++++++++++++++ src/ipam/util.rs | 49 ++++++++++++ src/lib.rs | 1 + 6 files changed, 343 insertions(+) create mode 100644 src/ipam/mod.rs create mode 100644 src/ipam/store.rs create mode 100644 src/ipam/util.rs diff --git a/Cargo.lock b/Cargo.lock index 43dc7c4b1..1ed767cda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -729,6 +747,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.9.0" @@ -965,6 +995,19 @@ name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.2", +] [[package]] name = "heck" @@ -1220,6 +1263,17 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1361,6 +1415,7 @@ dependencies = [ "once_cell", "prost", "rand", + "rusqlite", "serde", "serde-value", "serde_json", @@ -1621,6 +1676,12 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "polling" version = "2.8.0" @@ -1835,6 +1896,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags 2.4.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2388,6 +2463,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2682,6 +2763,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d075cf85bbb114e933343e087b92f2146bac0d55b534cbb8188becf0039948e" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/Cargo.toml b/Cargo.toml index fa4cd177f..73bce3f14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ deps-serde = ["chrono/serde", "url/serde"] [dependencies] anyhow = "1.0" +rusqlite = { version = "0.30.0", features = ["bundled"] } clap = { version = "~4.4.10", features = ["derive"] } env_logger = "0.10.1" ipnet = { version = "2", features = ["serde"] } diff --git a/src/ipam/mod.rs b/src/ipam/mod.rs new file mode 100644 index 000000000..3404eedfc --- /dev/null +++ b/src/ipam/mod.rs @@ -0,0 +1,2 @@ +pub mod store; +pub mod util; diff --git a/src/ipam/store.rs b/src/ipam/store.rs new file mode 100644 index 000000000..eb94de181 --- /dev/null +++ b/src/ipam/store.rs @@ -0,0 +1,189 @@ +use rusqlite::{Connection, Result}; +use std::net::IpAddr; +use std::path::Path; + +const NETAVARK_IPAM_DATABASE: &str = "netavark_ipam.sqlite"; + +#[derive(Debug)] +pub struct IpamEntry { + pub id: i32, + pub ip: IpAddr, + pub network: String, + pub ctr_id: String, +} + +pub fn get_ipamdb_connection(base: &str) -> Result { + let data_path = Path::new(&base).join("..").join(NETAVARK_IPAM_DATABASE); + let result = match Connection::open(data_path) { + Ok(conn) => conn, + Err(e) => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("unable to open ipam database: {e}"), + )); + } + }; + return Ok(result); +} + +pub fn create_ipam_table(conn: &Connection) { + _ = conn.execute( + "CREATE TABLE ipam ( + id INTEGER PRIMARY KEY, + ip BLOB, + network VARCHAR, + ctr_id VARCHAR + )", + (), + ); +} + +// All ip by network ordered by id, so first entry is the last ip address +pub fn get_ipam_entry_by_network(conn: &Connection, network: String) -> Result> { + let mut stmt = conn.prepare("SELECT * FROM ipam WHERE network = ?1 ORDER BY id DESC")?; + let ip_enteries = stmt.query_map([network], |row| { + let ip_raw: Vec = row.get(1)?; + let ipaddr: IpAddr; + if ip_raw.len() == 4 { + ipaddr = IpAddr::from( as TryInto<[u8; 4]>>::try_into(ip_raw).unwrap()); + } else { + ipaddr = IpAddr::from( as TryInto<[u8; 16]>>::try_into(ip_raw).unwrap()); + } + Ok(IpamEntry { + id: row.get(0)?, + ip: ipaddr, + network: row.get(2)?, + ctr_id: row.get(3)?, + }) + })?; + let mut result: Vec = Vec::new(); + for entry in ip_enteries { + match entry { + Ok(entry) => result.push(entry), + _ => {} + } + } + return Ok(result); +} + +// All ip by network ordered by id, so first entry is the last ip address +pub fn get_ipam_entry_by_ip(conn: &Connection, ip: IpAddr) -> Result> { + let mut stmt = conn.prepare("SELECT * FROM ipam WHERE ip = ?1 ORDER BY id DESC")?; + match ip { + IpAddr::V4(ip) => { + let ip_enteries = stmt.query_map([ip.octets()], |row| { + let ip_raw: Vec = row.get(1)?; + let ipaddr: IpAddr; + if ip_raw.len() == 4 { + ipaddr = IpAddr::from( as TryInto<[u8; 4]>>::try_into(ip_raw).unwrap()); + } else { + ipaddr = + IpAddr::from( as TryInto<[u8; 16]>>::try_into(ip_raw).unwrap()); + } + Ok(IpamEntry { + id: row.get(0)?, + ip: ipaddr, + network: row.get(2)?, + ctr_id: row.get(3)?, + }) + })?; + let mut result: Vec = Vec::new(); + for entry in ip_enteries { + match entry { + Ok(entry) => result.push(entry), + _ => {} + } + } + return Ok(result); + } + IpAddr::V6(ip) => { + let ip_enteries = stmt.query_map([ip.octets()], |row| { + let ip_raw: Vec = row.get(1)?; + let ipaddr: IpAddr; + if ip_raw.len() == 4 { + ipaddr = IpAddr::from( as TryInto<[u8; 4]>>::try_into(ip_raw).unwrap()); + } else { + ipaddr = + IpAddr::from( as TryInto<[u8; 16]>>::try_into(ip_raw).unwrap()); + } + Ok(IpamEntry { + id: row.get(0)?, + ip: ipaddr, + network: row.get(2)?, + ctr_id: row.get(3)?, + }) + })?; + let mut result: Vec = Vec::new(); + for entry in ip_enteries { + match entry { + Ok(entry) => result.push(entry), + _ => {} + } + } + return Ok(result); + } + } +} + +pub fn insert_ipam_entry(conn: &Connection, entry: IpamEntry) -> Result { + match entry.ip { + IpAddr::V4(ip) => { + return conn.execute( + "INSERT INTO ipam (ip, network, ctr_id) VALUES (?1, ?2, ?3)", + (ip.octets(), &entry.network, &entry.ctr_id), + ); + } + IpAddr::V6(ip) => { + return conn.execute( + "INSERT INTO ipam (ip, network, ctr_id) VALUES (?1, ?2, ?3)", + (ip.octets(), &entry.network, &entry.ctr_id), + ); + } + } +} + +pub fn delete_ipam_entry_by_ip(conn: &Connection, ip: IpAddr) -> Result { + match ip { + IpAddr::V4(ip) => { + return conn.execute("DELETE FROM ipam WHERE ip = ?1", [ip.octets()]); + } + IpAddr::V6(ip) => { + return conn.execute("DELETE FROM ipam WHERE ip = ?1", [ip.octets()]); + } + } +} + +pub fn delete_ipam_entry_by_network(conn: &Connection, network: String) -> Result { + return conn.execute("DELETE FROM ipam WHERE network = ?1", [network.as_str()]); +} + +pub fn delete_ipam_entry_by_ctr(conn: &Connection, id: String) -> Result { + return conn.execute("DELETE FROM ipam WHERE ctr_id = ?1", [id.as_str()]); +} + +pub fn get_all_ipam_entry(conn: &Connection) -> Result> { + let mut stmt = conn.prepare("SELECT * FROM ipam")?; + let ip_enteries = stmt.query_map([], |row| { + let ip_raw: Vec = row.get(1)?; + let ipaddr: IpAddr; + if ip_raw.len() == 4 { + ipaddr = IpAddr::from( as TryInto<[u8; 4]>>::try_into(ip_raw).unwrap()); + } else { + ipaddr = IpAddr::from( as TryInto<[u8; 16]>>::try_into(ip_raw).unwrap()); + } + Ok(IpamEntry { + id: row.get(0)?, + ip: ipaddr, + network: row.get(2)?, + ctr_id: row.get(3)?, + }) + })?; + let mut result: Vec = Vec::new(); + for entry in ip_enteries { + match entry { + Ok(entry) => result.push(entry), + _ => {} + } + } + return Ok(result); +} diff --git a/src/ipam/util.rs b/src/ipam/util.rs new file mode 100644 index 000000000..af8f48a4a --- /dev/null +++ b/src/ipam/util.rs @@ -0,0 +1,49 @@ +use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; + +pub fn next_ip(ip: &IpAddr) -> Option { + match ip { + IpAddr::V4(ipv4) => { + let ip = ipv4.octets(); + let ip_num = u32::from_be_bytes(ip); + let (ip_num, overflow) = ip_num.overflowing_add(1); + if overflow { + return None; + } + Some(IpAddr::V4(Ipv4Addr::from(ip_num.to_be_bytes()))) + } + IpAddr::V6(ipv6) => { + let ip = ipv6.octets(); + let ip_num = u128::from_be_bytes(ip); + let (ip_num, overflow) = ip_num.overflowing_add(1); + if overflow { + return None; + } + Some(IpAddr::V6(Ipv6Addr::from(ip_num.to_be_bytes()))) + } + } +} + +pub fn prev_ip(ip: &IpAddr) -> Option { + match ip { + IpAddr::V4(ipv4) => { + let ip = ipv4.octets(); + let ip_num = u32::from_be_bytes(ip); + let (ip_num, overflow) = ip_num.overflowing_sub(1); + if overflow { + return None; + } + Some(IpAddr::V4(Ipv4Addr::from(ip_num.to_be_bytes()))) + } + IpAddr::V6(ipv6) => { + let ip = ipv6.octets(); + let ip_num = u128::from_be_bytes(ip); + let (ip_num, overflow) = ip_num.overflowing_sub(1); + if overflow { + return None; + } + Some(IpAddr::V6(Ipv6Addr::from(ip_num.to_be_bytes()))) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2f3d24336..4bf4104bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,6 @@ pub mod dhcp_proxy; pub mod dns; pub mod error; pub mod firewall; +pub mod ipam; pub mod network; pub mod plugin;