From 04f507556baf2336a613cb684ec98f2cdf519163 Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Wed, 27 Nov 2019 16:59:54 +0100 Subject: Work on netlink IF event code for Linux --- src/platform/linux/tun.rs | 155 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 137 insertions(+), 18 deletions(-) (limited to 'src/platform') diff --git a/src/platform/linux/tun.rs b/src/platform/linux/tun.rs index 82eb469..442d9bc 100644 --- a/src/platform/linux/tun.rs +++ b/src/platform/linux/tun.rs @@ -1,13 +1,12 @@ use super::super::tun::*; -use libc::*; +use libc; use std::error::Error; use std::fmt; +use std::mem; use std::os::raw::c_short; use std::os::unix::io::RawFd; -use std::thread; -use std::time::Duration; const IFNAMSIZ: usize = 16; const TUNSETIFF: u64 = 0x4004_54ca; @@ -30,6 +29,18 @@ struct Ifreq { _pad: [u8; 64], } +// man 7 rtnetlink +// Layout from: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/rtnetlink.h#L516 +#[repr(C)] +struct IfInfomsg { + ifi_family: libc::c_uchar, + __ifi_pad: libc::c_uchar, + ifi_type: libc::c_ushort, + ifi_index: libc::c_int, + ifi_flags: libc::c_uint, + ifi_change: libc::c_uint, +} + pub struct LinuxTun { events: Vec, } @@ -42,12 +53,9 @@ pub struct LinuxTunWriter { fd: RawFd, } -/* Listens for netlink messages - * announcing an MTU update for the interface - */ -#[derive(Clone)] pub struct LinuxTunStatus { - first: bool, + events: Vec, + fd: RawFd, } #[derive(Debug)] @@ -94,7 +102,7 @@ impl Reader for LinuxTunReader { ); */ let n: isize = - unsafe { read(self.fd, buf[offset..].as_mut_ptr() as _, buf.len() - offset) }; + unsafe { libc::read(self.fd, buf[offset..].as_mut_ptr() as _, buf.len() - offset) }; if n < 0 { Err(LinuxTunError::Closed) } else { @@ -108,7 +116,7 @@ impl Writer for LinuxTunWriter { type Error = LinuxTunError; fn write(&self, src: &[u8]) -> Result<(), Self::Error> { - match unsafe { write(self.fd, src.as_ptr() as _, src.len() as _) } { + match unsafe { libc::write(self.fd, src.as_ptr() as _, src.len() as _) } { -1 => Err(LinuxTunError::Closed), _ => Ok(()), } @@ -119,13 +127,124 @@ impl Status for LinuxTunStatus { type Error = LinuxTunError; fn event(&mut self) -> Result { - if self.first { - self.first = false; - return Ok(TunEvent::Up(1420)); - } + const DONE: u16 = libc::NLMSG_DONE as u16; + const ERROR: u16 = libc::NLMSG_ERROR as u16; + const INFO_SIZE: usize = mem::size_of::(); + const HDR_SIZE: usize = mem::size_of::(); + let mut buf = [0u8; 1 << 12]; + log::debug!("netlink, fetch event (fd = {})", self.fd); loop { - thread::sleep(Duration::from_secs(60 * 60)); + // attempt to return a buffered event + if let Some(event) = self.events.pop() { + return Ok(event); + } + + // read message + let size: libc::ssize_t = + unsafe { libc::recv(self.fd, mem::transmute(&mut buf), buf.len(), 0) }; + if size < 0 { + break Err(LinuxTunError::Closed); + } + + // cut buffer to size + let size: usize = size as usize; + let mut remain = &buf[..size]; + log::debug!("netlink, recieved message ({} bytes)", size); + + // handle messages + while remain.len() >= HDR_SIZE { + // extract the header + assert!(remain.len() > HDR_SIZE); + let mut hdr = [0u8; HDR_SIZE]; + hdr.copy_from_slice(&remain[..HDR_SIZE]); + let hdr: libc::nlmsghdr = unsafe { mem::transmute(hdr) }; + + // upcast length + let body: &[u8] = &remain[HDR_SIZE..]; + let msg_len: usize = hdr.nlmsg_len as usize; + assert!(msg_len <= remain.len(), "malformed netlink message"); + + // handle message body + match hdr.nlmsg_type { + DONE => break, + ERROR => break, + libc::RTM_NEWLINK => { + // extract info struct + if body.len() < INFO_SIZE { + return Err(LinuxTunError::Closed); + } + + let mut info = [0u8; INFO_SIZE]; + info.copy_from_slice(&body[..INFO_SIZE]); + log::debug!("netlink, RTM_NEWLINK {:?}", &info[..]); + let info: IfInfomsg = unsafe { mem::transmute(info) }; + + // trace log + log::trace!( + "netlink, IfInfomsg{{ family = {}, type = {}, index = {}, flags = {}, change = {}}}", + info.ifi_family, + info.ifi_type, + info.ifi_index, + info.ifi_flags, + info.ifi_change, + ); + debug_assert_eq!(info.__ifi_pad, 0); + + // handle up / down + if info.ifi_flags & (libc::IFF_UP as u32) != 0 { + log::trace!("netlink, up event"); + self.events.push(TunEvent::Up(1420)); + } else { + log::trace!("netlink, down event"); + self.events.push(TunEvent::Down); + } + } + _ => (), + }; + + // go to next message + remain = &remain[msg_len..]; + } + } + } +} + +impl LinuxTunStatus { + const RTNLGRP_LINK: libc::c_uint = 1; + const RTNLGRP_IPV4_IFADDR: libc::c_uint = 5; + const RTNLGRP_IPV6_IFADDR: libc::c_uint = 9; + + fn new() -> Result { + // create netlink socket + let fd = unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) }; + if fd < 0 { + return Err(LinuxTunError::Closed); + } + + // prepare address (specify groups) + let groups = (1 << (Self::RTNLGRP_LINK - 1)) + | (1 << (Self::RTNLGRP_IPV4_IFADDR - 1)) + | (1 << (Self::RTNLGRP_IPV6_IFADDR - 1)); + + let mut sockaddr: libc::sockaddr_nl = unsafe { mem::zeroed() }; + sockaddr.nl_family = libc::AF_NETLINK as u16; + sockaddr.nl_groups = groups; + sockaddr.nl_pid = 0; + + // attempt to bind + let res = unsafe { + libc::bind( + fd, + mem::transmute(&mut sockaddr), + mem::size_of::() as u32, + ) + }; + + if res != 0 { + Err(LinuxTunError::Closed) + } else { + Ok(LinuxTunStatus { events: vec![], fd }) } } } @@ -155,14 +274,14 @@ impl PlatformTun for LinuxTun { req.name[..bs.len()].copy_from_slice(bs); // open clone device - let fd: RawFd = match unsafe { open(CLONE_DEVICE_PATH.as_ptr() as _, O_RDWR) } { + let fd: RawFd = match unsafe { libc::open(CLONE_DEVICE_PATH.as_ptr() as _, libc::O_RDWR) } { -1 => return Err(LinuxTunError::FailedToOpenCloneDevice), fd => fd, }; assert!(fd >= 0); // create TUN device - if unsafe { ioctl(fd, TUNSETIFF as _, &req) } < 0 { + if unsafe { libc::ioctl(fd, TUNSETIFF as _, &req) } < 0 { return Err(LinuxTunError::SetIFFIoctlFailed); } @@ -170,7 +289,7 @@ impl PlatformTun for LinuxTun { Ok(( vec![LinuxTunReader { fd }], // TODO: enable multi-queue for Linux LinuxTunWriter { fd }, - LinuxTunStatus { first: true }, + LinuxTunStatus::new()?, )) } } -- cgit v1.2.3-59-g8ed1b