diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-11-14 17:35:54 +0100 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-11-14 17:35:54 +0100 |
commit | 1c2a4a8ace82f3ed0696e112b0434301538b3500 (patch) | |
tree | cfa2eb80f7f1cbcbbc67ba6b2f7bfbb568b5cdba | |
parent | e1d63ff533bd1b56ac5394635c96aa354308d859 (diff) |
initial split
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/addr.rs | 189 | ||||
-rw-r--r-- | src/error.rs | 27 | ||||
-rw-r--r-- | src/lib.rs | 19 | ||||
-rw-r--r-- | src/link.rs | 194 | ||||
-rw-r--r-- | src/route.rs | 167 | ||||
-rw-r--r-- | src/tunnel.rs | 297 |
7 files changed, 888 insertions, 13 deletions
@@ -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>; @@ -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, +} |