aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHimbeerserverDE <himbeerserverde@gmail.com>2023-11-14 17:35:54 +0100
committerHimbeerserverDE <himbeerserverde@gmail.com>2023-11-14 17:35:54 +0100
commit1c2a4a8ace82f3ed0696e112b0434301538b3500 (patch)
treecfa2eb80f7f1cbcbbc67ba6b2f7bfbb568b5cdba
parente1d63ff533bd1b56ac5394635c96aa354308d859 (diff)
initial split
-rw-r--r--Cargo.toml8
-rw-r--r--src/addr.rs189
-rw-r--r--src/error.rs27
-rw-r--r--src/lib.rs19
-rw-r--r--src/link.rs194
-rw-r--r--src/route.rs167
-rw-r--r--src/tunnel.rs297
7 files changed, 888 insertions, 13 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 2ac468a..359ead8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+futures = { version = "0.3.11", default-features = false, features = ["std"] }
+ipnet = "2.9.0"
+libc = "0.2.150"
+netlink-packet-route = "^0.17"
+rtnetlink = { version = "0.13.1" }
+serde_json = "1.0"
+thiserror = "1.0"
+tokio = "1.0"
diff --git a/src/addr.rs b/src/addr.rs
new file mode 100644
index 0000000..5c3ba34
--- /dev/null
+++ b/src/addr.rs
@@ -0,0 +1,189 @@
+use crate::{Error, Result};
+
+use std::net::IpAddr;
+
+use futures::{future, TryStreamExt};
+use netlink_packet_route::{AddressMessage, AF_INET, AF_INET6, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE};
+use tokio::runtime::Runtime;
+
+async fn do_flush(link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let addrs: Vec<AddressMessage> = handle
+ .address()
+ .get()
+ .set_link_index_filter(id)
+ .execute()
+ .try_collect()
+ .await?;
+
+ for addr in addrs {
+ handle.address().del(addr).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush(link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_flush(link))
+// }
+
+async fn do_flush4(link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let addrs: Vec<AddressMessage> = handle
+ .address()
+ .get()
+ .set_link_index_filter(id)
+ .execute()
+ .try_filter(|addr| future::ready(addr.header.family == AF_INET as u8))
+ .try_collect()
+ .await?;
+
+ for addr in addrs {
+ handle.address().del(addr).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush4(link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_flush4(link))
+// }
+
+async fn do_flush6(link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let addrs: Vec<AddressMessage> = handle
+ .address()
+ .get()
+ .set_link_index_filter(id)
+ .execute()
+ .try_filter(|addr| future::ready(addr.header.family == AF_INET6 as u8))
+ .try_collect()
+ .await?;
+
+ for addr in addrs {
+ handle.address().del(addr).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush6(link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_flush6(link))
+// }
+
+async fn do_flush6_global() -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let addrs: Vec<AddressMessage> = handle
+ .address()
+ .get()
+ .execute()
+ .try_filter(|addr| {
+ future::ready(
+ addr.header.family == AF_INET6 as u8 && addr.header.scope == RT_SCOPE_UNIVERSE,
+ )
+ })
+ .try_collect()
+ .await?;
+
+ for addr in addrs {
+ handle.address().del(addr).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush6_global() -> Result<()> {
+// Runtime::new()?.block_on(do_flush6_global())
+// }
+
+async fn do_add(link: String, addr: IpAddr, prefix_len: u8) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ handle.address().add(id, addr, prefix_len).execute().await?;
+
+ Ok(())
+}
+
+// pub fn add(link: String, addr: IpAddr, prefix_len: u8) -> Result<()> {
+// Runtime::new()?.block_on(do_add(link, addr, prefix_len))
+// }
+
+async fn do_add_link_local(link: String, addr: IpAddr, prefix_len: u8) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let mut req = handle.address().add(id, addr, prefix_len);
+ req.message_mut().header.scope = RT_SCOPE_LINK;
+
+ req.execute().await?;
+
+ Ok(())
+}
+
+// pub fn add_link_local(link: String, addr: IpAddr, prefix_len: u8) -> Result<()> {
+// Runtime::new()?.block_on(do_add_link_local(link, addr, prefix_len))
+// }
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..26e2444
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,27 @@
+use std::{ffi, io, net};
+
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("link {0} not found")]
+ LinkNotFound(String),
+ #[error("not enough ipv6 subnets")]
+ NotEnoughIpv6Subnets,
+
+ #[error("ffi nul: {0}")]
+ Nul(#[from] ffi::NulError),
+ #[error("io: {0}")]
+ Io(#[from] io::Error),
+
+ #[error("ipnet prefix len: {0}")]
+ IpnetPrefixLen(#[from] ipnet::PrefixLenError),
+ #[error("net: parse ip address: {0}")]
+ NetAddrParseError(#[from] net::AddrParseError),
+ #[error("rtnetlink: {0}")]
+ RtNetlink(#[from] rtnetlink::Error),
+ #[error("serde_json: {0}")]
+ SerdeJson(#[from] serde_json::Error),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/lib.rs b/src/lib.rs
index 7d12d9a..4f06d33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,7 @@
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
+mod error;
+pub use error::*;
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn it_works() {
- let result = add(2, 2);
- assert_eq!(result, 4);
- }
-}
+pub mod addr;
+pub mod link;
+pub mod route;
+pub mod tunnel;
diff --git a/src/link.rs b/src/link.rs
new file mode 100644
index 0000000..d936dd9
--- /dev/null
+++ b/src/link.rs
@@ -0,0 +1,194 @@
+use crate::{Error, Result};
+
+use std::num::NonZeroI32;
+use std::thread;
+use std::time::Duration;
+
+use futures::TryStreamExt;
+use netlink_packet_route::rtnl::IFF_UP;
+use rtnetlink::Error::NetlinkError;
+use tokio::runtime::Runtime;
+
+#[derive(Clone, Copy, Debug)]
+enum State {
+ Up,
+ Down,
+}
+
+async fn set(link: String, state: State) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ match state {
+ State::Up => handle.link().set(id).up(),
+ State::Down => handle.link().set(id).down(),
+ }
+ .execute()
+ .await?;
+
+ Ok(())
+}
+
+// pub fn up(link: String) -> Result<()> {
+// Runtime::new()?.block_on(set(link, State::Up))
+// }
+//
+// pub fn down(link: String) -> Result<()> {
+// Runtime::new()?.block_on(set(link, State::Down))
+// }
+
+async fn do_is_up(link: String) -> Result<bool> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let is_up = link.header.flags & IFF_UP == IFF_UP;
+ Ok(is_up)
+}
+
+// pub fn is_up(link: String) -> Result<bool> {
+// Runtime::new()?.block_on(do_is_up(link))
+// }
+
+async fn do_set_mtu(link: String, mtu: u32) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ handle.link().set(id).mtu(mtu).execute().await?;
+ Ok(())
+}
+
+// pub fn set_mtu(link: String, mtu: u32) -> Result<()> {
+// Runtime::new()?.block_on(do_set_mtu(link, mtu))
+// }
+
+async fn do_add_vlan(link: String, parent: String, vlan_id: u16) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let parent = handle
+ .link()
+ .get()
+ .match_name(parent.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(parent))?;
+
+ let parent_id = parent.header.index;
+
+ handle
+ .link()
+ .add()
+ .vlan(link, parent_id, vlan_id)
+ .execute()
+ .await?;
+
+ Ok(())
+}
+
+// pub fn add_vlan(link: String, parent: String, vlan_id: u16) -> Result<()> {
+// Runtime::new()?.block_on(do_add_vlan(link, parent, vlan_id))
+// }
+
+// pub fn wait_up(link: String) -> Result<()> {
+// while !match is_up(link.clone()) {
+// Ok(v) => v,
+// Err(e) => {
+// if let Error::LinkNotFound(_) = e {
+// false
+// } else if let Error::RtNetlink(NetlinkError(ref msg)) = e {
+// // Error -19 is "No such device".
+// if msg.code == NonZeroI32::new(-19) {
+// false
+// } else {
+// return Err(e);
+// }
+// } else {
+// return Err(e);
+// }
+// }
+// } {
+// thread::sleep(Duration::from_secs(1));
+// }
+//
+// Ok(())
+// }
+
+async fn do_exists(link: String) -> Result<bool> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let exists = handle
+ .link()
+ .get()
+ .match_name(link)
+ .execute()
+ .try_next()
+ .await
+ .is_ok();
+
+ Ok(exists)
+}
+
+// pub fn exists(link: String) -> Result<bool> {
+// Runtime::new()?.block_on(do_exists(link))
+// }
+//
+// pub fn wait_exists(link: String) -> Result<()> {
+// while !exists(link.clone())? {
+// thread::sleep(Duration::from_secs(1));
+// }
+//
+// Ok(())
+// }
+
+async fn do_index(link: String) -> Result<u32> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ Ok(link.header.index)
+}
+
+// pub fn index(link: String) -> Result<u32> {
+// Runtime::new()?.block_on(do_index(link))
+// }
diff --git a/src/route.rs b/src/route.rs
new file mode 100644
index 0000000..b180e96
--- /dev/null
+++ b/src/route.rs
@@ -0,0 +1,167 @@
+use crate::{Error, Result};
+
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+use futures::{future, TryStreamExt};
+use netlink_packet_route::{RouteMessage, RT_SCOPE_LINK};
+use rtnetlink::IpVersion;
+use tokio::runtime::Runtime;
+
+async fn do_flush4(link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let routes: Vec<RouteMessage> = handle
+ .route()
+ .get(IpVersion::V4)
+ .execute()
+ .try_filter(|route| {
+ future::ready(if let Some(ifi) = route.output_interface() {
+ ifi == id
+ } else {
+ false
+ })
+ })
+ .try_collect()
+ .await?;
+
+ for route in routes {
+ handle.route().del(route).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush4(link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_flush4(link))
+// }
+
+async fn do_flush6(link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let routes: Vec<RouteMessage> = handle
+ .route()
+ .get(IpVersion::V6)
+ .execute()
+ .try_filter(|route| {
+ future::ready(if let Some(ifi) = route.output_interface() {
+ ifi == id
+ } else {
+ false
+ })
+ })
+ .try_collect()
+ .await?;
+
+ for route in routes {
+ handle.route().del(route).execute().await?;
+ }
+
+ Ok(())
+}
+
+// pub fn flush6(link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_flush6(link))
+// }
+//
+// pub fn flush(link: String) -> Result<()> {
+// flush4(link.clone())?;
+// flush6(link)?;
+//
+// Ok(())
+// }
+
+async fn do_add4(dst: Ipv4Addr, prefix_len: u8, rtr: Option<Ipv4Addr>, link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let mut add = handle
+ .route()
+ .add()
+ .v4()
+ .destination_prefix(dst, prefix_len)
+ .output_interface(id);
+
+ if let Some(rtr) = rtr {
+ add = add.gateway(rtr);
+ } else {
+ add = add.scope(RT_SCOPE_LINK);
+ }
+
+ add.execute().await?;
+ Ok(())
+}
+
+// pub fn add4(dst: Ipv4Addr, prefix_len: u8, rtr: Option<Ipv4Addr>, link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_add4(dst, prefix_len, rtr, link))
+// }
+
+async fn do_add6(dst: Ipv6Addr, prefix_len: u8, rtr: Option<Ipv6Addr>, link: String) -> Result<()> {
+ let (conn, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(conn);
+
+ let link = handle
+ .link()
+ .get()
+ .match_name(link.clone())
+ .execute()
+ .try_next()
+ .await?
+ .ok_or(Error::LinkNotFound(link))?;
+
+ let id = link.header.index;
+
+ let mut add = handle
+ .route()
+ .add()
+ .v6()
+ .destination_prefix(dst, prefix_len)
+ .output_interface(id);
+
+ if let Some(rtr) = rtr {
+ add = add.gateway(rtr);
+ } else {
+ add = add.scope(RT_SCOPE_LINK);
+ }
+
+ add.execute().await?;
+ Ok(())
+}
+
+// pub fn add6(dst: Ipv6Addr, prefix_len: u8, rtr: Option<Ipv6Addr>, link: String) -> Result<()> {
+// Runtime::new()?.block_on(do_add6(dst, prefix_len, rtr, link))
+// }
diff --git a/src/tunnel.rs b/src/tunnel.rs
new file mode 100644
index 0000000..cfab916
--- /dev/null
+++ b/src/tunnel.rs
@@ -0,0 +1,297 @@
+use crate::{Error, Result};
+
+use std::ffi::{c_char, c_int, CString};
+use std::io;
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+const SIOCADDTUNNEL: c_int = 0x89F0 + 1;
+const SIOCDELTUNNEL: c_int = 0x89F0 + 2;
+
+/// A handle to a 6in4 tunnel. The interface is automatically deleted on drop.
+#[derive(Debug)]
+pub struct Sit {
+ name: String,
+}
+
+impl Drop for Sit {
+ fn drop(&mut self) {
+ let _ = self.do_delete();
+ }
+}
+
+impl Sit {
+ pub fn new(name: String, master: String, laddr: Ipv4Addr, raddr: Ipv4Addr) -> Result<Self> {
+ let tnlname = CString::new(&*name)?;
+ let ifmaster = CString::new(&*master)?;
+ let sit0 = CString::new("sit0")?;
+
+ let tnlname_signed = unsafe { &*(tnlname.as_bytes() as *const _ as *const [i8]) };
+ let mut tnlname_arr = [0i8; libc::IFNAMSIZ];
+ for (&i, o) in tnlname_signed.iter().zip(tnlname_arr.iter_mut()) {
+ *o = i;
+ }
+
+ let sit0_signed = unsafe { &*(sit0.as_bytes() as *const _ as *const [i8]) };
+ let mut sit0_arr = [0i8; libc::IFNAMSIZ];
+ for (&i, o) in sit0_signed.iter().zip(sit0_arr.iter_mut()) {
+ *o = i;
+ }
+
+ let mut vihl = VerIhl::default();
+
+ vihl.set_version(4);
+ vihl.set_ihl(5);
+
+ let p = IpTunnelParm4 {
+ name: tnlname_arr,
+ link: unsafe { libc::if_nametoindex(ifmaster.as_ptr()) },
+ i_flags: 0,
+ o_flags: 0,
+ i_key: 0,
+ o_key: 0,
+ iph: IpHdr4 {
+ vihl,
+ tos: 0,
+ tot_len: 0,
+ id: 0,
+ frag_off: 0,
+ check: 0,
+ ttl: 64,
+ protocol: libc::IPPROTO_IPV6 as u8,
+ saddr: u32::from(laddr).to_be(),
+ daddr: u32::from(raddr).to_be(),
+ },
+ };
+
+ if p.link == 0 {
+ return Err(Error::LinkNotFound(master));
+ }
+
+ let ifr = IfReq4 {
+ name: sit0_arr,
+ ifru_data: &p,
+ };
+
+ let fd = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP) };
+ if fd < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ if unsafe { libc::ioctl(fd, SIOCADDTUNNEL, &ifr) } < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ // Errors are safe to ignore because they don't affect tunnel creation
+ // but do leave the program in an inconsistent state.
+ unsafe {
+ libc::close(fd);
+ }
+
+ Ok(Self { name })
+ }
+
+ fn do_delete(&self) -> Result<()> {
+ delete_tunnel(&self.name)
+ }
+}
+
+/// A handle to a 4in6 tunnel. The interface is automatically deleted on drop.
+#[derive(Debug)]
+pub struct IpIp6 {
+ name: String,
+}
+
+impl Drop for IpIp6 {
+ fn drop(&mut self) {
+ let _ = self.do_delete();
+ }
+}
+
+impl IpIp6 {
+ pub fn new(name: String, master: String, laddr: Ipv6Addr, raddr: Ipv6Addr) -> Result<Self> {
+ let tnlname = CString::new(&*name)?;
+ let ifmaster = CString::new(&*master)?;
+ let ip6tnl0 = CString::new("ip6tnl0")?;
+
+ let tnlname_signed = unsafe { &*(tnlname.as_bytes() as *const _ as *const [i8]) };
+ let mut tnlname_arr = [0i8; libc::IFNAMSIZ];
+ for (&i, o) in tnlname_signed.iter().zip(tnlname_arr.iter_mut()) {
+ *o = i;
+ }
+
+ let ip6tnl0_signed = unsafe { &*(ip6tnl0.as_bytes() as *const _ as *const [i8]) };
+ let mut ip6tnl0_arr = [0i8; libc::IFNAMSIZ];
+ for (&i, o) in ip6tnl0_signed.iter().zip(ip6tnl0_arr.iter_mut()) {
+ *o = i;
+ }
+
+ let p = IpTunnelParm6 {
+ name: tnlname_arr,
+ link: unsafe { libc::if_nametoindex(ifmaster.as_ptr()) },
+ i_flags: 0,
+ o_flags: 0,
+ i_key: 0,
+ o_key: 0,
+ iph: IpHdr6 {
+ saddr: u128::from(laddr).to_be(),
+ daddr: u128::from(raddr).to_be(),
+ },
+ };
+
+ if p.link == 0 {
+ return Err(Error::LinkNotFound(master));
+ }
+
+ let ifr = IfReq6 {
+ name: ip6tnl0_arr,
+ ifru_data: &p,
+ };
+
+ let fd = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_IP) };
+ if fd < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ if unsafe { libc::ioctl(fd, SIOCADDTUNNEL, &ifr) } < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ // Errors are safe to ignore because they don't affect tunnel creation
+ // but do leave the program in an inconsistent state.
+ unsafe {
+ libc::close(fd);
+ }
+
+ Ok(Self { name })
+ }
+
+ fn do_delete(&self) -> Result<()> {
+ delete_tunnel(&self.name)
+ }
+}
+
+fn delete_tunnel(name: &str) -> Result<()> {
+ let tnlname = CString::new(name)?;
+
+ let tnlname_signed = unsafe { &*(tnlname.as_bytes() as *const _ as *const [i8]) };
+ let mut tnlname_arr = [0i8; libc::IFNAMSIZ];
+ for (&i, o) in tnlname_signed.iter().zip(tnlname_arr.iter_mut()) {
+ *o = i;
+ }
+
+ let p = IpTunnelParm4 {
+ name: tnlname_arr,
+ link: 0,
+ i_flags: 0,
+ o_flags: 0,
+ i_key: 0,
+ o_key: 0,
+ iph: IpHdr4 {
+ vihl: VerIhl::default(),
+ tos: 0,
+ tot_len: 0,
+ id: 0,
+ frag_off: 0,
+ ttl: 0,
+ protocol: 0,
+ check: 0,
+ saddr: 0,
+ daddr: 0,
+ },
+ };
+
+ let ifr = IfReq4 {
+ name: tnlname_arr,
+ ifru_data: &p,
+ };
+
+ let fd = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_IP) };
+ if fd < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ if unsafe { libc::ioctl(fd, SIOCDELTUNNEL, &ifr) } < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ // Errors are safe to ignore because they don't affect tunnel deletion
+ // but do leave the program in an inconsistent state.
+ unsafe {
+ libc::close(fd);
+ }
+
+ Ok(())
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct VerIhl(u8);
+
+impl VerIhl {
+ fn set_version(&mut self, version: u8) {
+ self.0 = (self.0 & 0x0f) | (version << 4);
+ }
+
+ fn set_ihl(&mut self, ihl: u8) {
+ self.0 = (self.0 & 0xf0) | (ihl % 0x0f);
+ }
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IpHdr4 {
+ vihl: VerIhl,
+ tos: u8,
+ tot_len: u16,
+ id: u16,
+ frag_off: u16,
+ ttl: u8,
+ protocol: u8,
+ check: u16,
+ saddr: u32,
+ daddr: u32,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IpTunnelParm4 {
+ name: [c_char; libc::IFNAMSIZ],
+ link: u32,
+ i_flags: u16,
+ o_flags: u16,
+ i_key: u32,
+ o_key: u32,
+ iph: IpHdr4,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IfReq4 {
+ name: [c_char; libc::IFNAMSIZ],
+ ifru_data: *const IpTunnelParm4,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IpHdr6 {
+ saddr: u128,
+ daddr: u128,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IpTunnelParm6 {
+ name: [c_char; libc::IFNAMSIZ],
+ link: u32,
+ i_flags: u16,
+ o_flags: u16,
+ i_key: u32,
+ o_key: u32,
+ iph: IpHdr6,
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct IfReq6 {
+ name: [c_char; libc::IFNAMSIZ],
+ ifru_data: *const IpTunnelParm6,
+}