diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f08cd94 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "tun" +version = "0.5.4" +edition = "2018" + +authors = ["meh. "] +license = "WTFPL" + +description = "TUN device creation and handling." +repository = "https://github.com/meh/rust-tun" +keywords = ["tun", "network", "tunnel", "bindings"] + +[dependencies] +libc = "0.2" +thiserror = "1" + +[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "android"))'.dependencies] +tokio = { version = "1", features = ["net", "macros"], optional = true } +tokio-util = { version = "0.6", features = ["codec"], optional = true } +bytes = { version = "1", optional = true } +byteorder = { version = "1", optional = true } +# This is only for the `ready` macro. +futures-core = { version = "0.3", optional = true } + +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] +ioctl = { version = "0.6", package = "ioctl-sys" } + +[dev-dependencies] +packet = "0.1" +futures = "0.3" + +[features] +async = ["tokio", "tokio-util", "bytes", "byteorder", "futures-core"] + +[[example]] +name = "read-async" +required-features = [ "async", "tokio/rt-multi-thread" ] + +[[example]] +name = "read-async-codec" +required-features = [ "async", "tokio/rt-multi-thread" ] + +[[example]] +name = "ping-tun" +required-features = [ "async", "tokio/rt-multi-thread" ] diff --git a/README.md b/README.md index f643922..ae24a9e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,106 @@ -# meh-rust-tun +TUN interfaces [![Crates.io](https://img.shields.io/crates/v/tun.svg)](https://crates.io/crates/tun) ![tun](https://docs.rs/tun/badge.svg) ![WTFPL](http://img.shields.io/badge/license-WTFPL-blue.svg) +============== +This crate allows the creation and usage of TUN interfaces, the aim is to make this cross-platform. -https://github.com/meh/rust-tun \ No newline at end of file +Usage +----- +First, add the following to your `Cargo.toml`: + +```toml +[dependencies] +tun = "0.5" +``` + +Next, add this to your crate root: + +```rust +extern crate tun; +``` + +If you want to use the TUN interface with mio/tokio, you need to enable the `async` feature: + +```toml +[dependencies] +tun = { version = "0.5", features = ["async"] } +``` + +Example +------- +The following example creates and configures a TUN interface and starts reading +packets from it. + +```rust +use std::io::Read; + +extern crate tun; + +fn main() { + let mut config = tun::Configuration::default(); + config.address((10, 0, 0, 1)) + .netmask((255, 255, 255, 0)) + .up(); + + #[cfg(target_os = "linux")] + config.platform(|config| { + config.packet_information(true); + }); + + let mut dev = tun::create(&config).unwrap(); + let mut buf = [0; 4096]; + + loop { + let amount = dev.read(&mut buf).unwrap(); + println!("{:?}", &buf[0 .. amount]); + } +} +``` + +Platforms +========= +Not every platform is supported. + +Linux +----- +You will need the `tun` module to be loaded and root is required to create +interfaces. + +macOS +----- +It just werks, but you have to set up routing manually. + +iOS +---- +You can pass the file descriptor of the TUN device to `rust-tun` to create the interface. + +Here is an example to create the TUN device on iOS and pass the `fd` to `rust-tun`: +```swift +// Swift +class PacketTunnelProvider: NEPacketTunnelProvider { + override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + let tunnelNetworkSettings = createTunnelSettings() // Configure TUN address, DNS, mtu, routing... + setTunnelNetworkSettings(tunnelNetworkSettings) { [weak self] error in + let tunFd = self?.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32 + DispatchQueue.global(qos: .default).async { + start_tun(tunFd) + } + completionHandler(nil) + } + } +} +``` + +```rust +#[no_mangle] +pub extern "C" fn start_tun(fd: std::os::raw::c_int) { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut cfg = tun::Configuration::default(); + cfg.raw_fd(fd); + let mut tun = tun::create_as_async(&cfg).unwrap(); + let mut framed = tun.into_framed(); + while let Some(packet) = framed.next().await { + ... + } + }); +} +``` diff --git a/examples/ping-tun.rs b/examples/ping-tun.rs new file mode 100644 index 0000000..a7a7c41 --- /dev/null +++ b/examples/ping-tun.rs @@ -0,0 +1,78 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use futures::{SinkExt, StreamExt}; +use packet::{builder::Builder, icmp, ip, Packet}; +use tun::{self, Configuration, TunPacket}; + +#[tokio::main] +async fn main() { + let mut config = Configuration::default(); + + config + .address((10, 0, 0, 1)) + .netmask((255, 255, 255, 0)) + .up(); + + #[cfg(target_os = "linux")] + config.platform(|config| { + config.packet_information(true); + }); + + let dev = tun::create_as_async(&config).unwrap(); + + let mut framed = dev.into_framed(); + + while let Some(packet) = framed.next().await { + match packet { + Ok(pkt) => match ip::Packet::new(pkt.get_bytes()) { + Ok(ip::Packet::V4(pkt)) => match icmp::Packet::new(pkt.payload()) { + Ok(icmp) => match icmp.echo() { + Ok(icmp) => { + let reply = ip::v4::Builder::default() + .id(0x42) + .unwrap() + .ttl(64) + .unwrap() + .source(pkt.destination()) + .unwrap() + .destination(pkt.source()) + .unwrap() + .icmp() + .unwrap() + .echo() + .unwrap() + .reply() + .unwrap() + .identifier(icmp.identifier()) + .unwrap() + .sequence(icmp.sequence()) + .unwrap() + .payload(icmp.payload()) + .unwrap() + .build() + .unwrap(); + framed.send(TunPacket::new(reply)).await.unwrap(); + } + _ => {} + }, + _ => {} + }, + Err(err) => println!("Received an invalid packet: {:?}", err), + _ => {} + }, + Err(err) => panic!("Error: {:?}", err), + } + } +} diff --git a/examples/read-async-codec.rs b/examples/read-async-codec.rs new file mode 100644 index 0000000..211ab50 --- /dev/null +++ b/examples/read-async-codec.rs @@ -0,0 +1,61 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use bytes::BytesMut; +use futures::StreamExt; +use packet::{ip::Packet, Error}; +use tokio_util::codec::{Decoder, FramedRead}; + +pub struct IPPacketCodec; + +impl Decoder for IPPacketCodec { + type Item = Packet; + type Error = Error; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + if buf.is_empty() { + return Ok(None); + } + + let buf = buf.split_to(buf.len()); + Ok(match Packet::no_payload(buf) { + Ok(pkt) => Some(pkt), + Err(err) => { + println!("error {:?}", err); + None + } + }) + } +} + +#[tokio::main] +async fn main() { + let mut config = tun::Configuration::default(); + + config + .address((10, 0, 0, 1)) + .netmask((255, 255, 255, 0)) + .up(); + + let dev = tun::create_as_async(&config).unwrap(); + + let mut stream = FramedRead::new(dev, IPPacketCodec); + + while let Some(packet) = stream.next().await { + match packet { + Ok(pkt) => println!("pkt: {:#?}", pkt), + Err(err) => panic!("Error: {:?}", err), + } + } +} diff --git a/examples/read-async.rs b/examples/read-async.rs new file mode 100644 index 0000000..9fe9def --- /dev/null +++ b/examples/read-async.rs @@ -0,0 +1,42 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use futures::StreamExt; +use packet::ip::Packet; + +#[tokio::main] +async fn main() { + let mut config = tun::Configuration::default(); + + config + .address((10, 0, 0, 1)) + .netmask((255, 255, 255, 0)) + .up(); + + #[cfg(target_os = "linux")] + config.platform(|config| { + config.packet_information(true); + }); + + let dev = tun::create_as_async(&config).unwrap(); + + let mut stream = dev.into_framed(); + + while let Some(packet) = stream.next().await { + match packet { + Ok(pkt) => println!("pkt: {:#?}", Packet::unchecked(pkt.get_bytes())), + Err(err) => panic!("Error: {:?}", err), + } + } +} diff --git a/examples/read.rs b/examples/read.rs new file mode 100644 index 0000000..63ffd53 --- /dev/null +++ b/examples/read.rs @@ -0,0 +1,37 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io::Read; + +fn main() { + let mut config = tun::Configuration::default(); + + config + .address((10, 0, 0, 1)) + .netmask((255, 255, 255, 0)) + .up(); + + #[cfg(target_os = "linux")] + config.platform(|config| { + config.packet_information(true); + }); + + let mut dev = tun::create(&config).unwrap(); + let mut buf = [0; 4096]; + + loop { + let amount = dev.read(&mut buf).unwrap(); + println!("{:?}", &buf[0..amount]); + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..63f4507 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1629858211, + "narHash": "sha256-YyQvCmRekZ5QGN9RObUZWg6Pqmi5+idTZ5Ew1Ct5tEk=", + "owner": "nix-community", + "repo": "fenix", + "rev": "495510be4bc7ccf03b54ff4226fcc7b01fcf62ee", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1629827819, + "narHash": "sha256-cysfDbdjTluO+UtuT6iRrf/2Lt7Qo/qRlpz9F5rz3Sc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "503209808cd613daed238e21e7a18ffcbeacebe3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1629817082, + "narHash": "sha256-qdKZss+bNccS8FJsKbkVGZGISiAGlQ/Se1PFlcroVEk=", + "owner": "rust-analyzer", + "repo": "rust-analyzer", + "rev": "ce4670f299d72a0f23f347b5df9790aca72da617", + "type": "github" + }, + "original": { + "owner": "rust-analyzer", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "utils": { + "locked": { + "lastModified": 1629481132, + "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "997f7efcb746a9c140ce1f13c72263189225f482", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..70f19ab --- /dev/null +++ b/flake.nix @@ -0,0 +1,28 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, utils, nixpkgs, fenix, }: utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + rust = fenix.packages.${system}; + lib = pkgs.lib; + in { + devShell = pkgs.mkShell { + buildInputs = with pkgs; with llvmPackages; [ + # For building. + clang rust.stable.toolchain pkg-config openssl libsodium libclang.lib + ]; + + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + RUST_BACKTRACE = 1; + # RUST_LOG = "info,sqlx::query=warn"; + RUSTFLAGS = "-C target-cpu=native"; + }; + }); +} diff --git a/src/address.rs b/src/address.rs new file mode 100644 index 0000000..fd85fd4 --- /dev/null +++ b/src/address.rs @@ -0,0 +1,128 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::net::{IpAddr, Ipv4Addr}; +use std::net::{SocketAddr, SocketAddrV4}; + +use crate::error::*; + +/// Helper trait to convert things into IPv4 addresses. +#[allow(clippy::wrong_self_convention)] +pub trait IntoAddress { + /// Convert the type to an `Ipv4Addr`. + fn into_address(&self) -> Result; +} + +impl IntoAddress for u32 { + fn into_address(&self) -> Result { + Ok(Ipv4Addr::new( + ((*self) & 0xff) as u8, + ((*self >> 8) & 0xff) as u8, + ((*self >> 16) & 0xff) as u8, + ((*self >> 24) & 0xff) as u8, + )) + } +} + +impl IntoAddress for i32 { + fn into_address(&self) -> Result { + (*self as u32).into_address() + } +} + +impl IntoAddress for (u8, u8, u8, u8) { + fn into_address(&self) -> Result { + Ok(Ipv4Addr::new(self.0, self.1, self.2, self.3)) + } +} + +impl IntoAddress for str { + fn into_address(&self) -> Result { + self.parse().map_err(|_| Error::InvalidAddress) + } +} + +impl<'a> IntoAddress for &'a str { + fn into_address(&self) -> Result { + (*self).into_address() + } +} + +impl IntoAddress for String { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} + +impl<'a> IntoAddress for &'a String { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} + +impl IntoAddress for Ipv4Addr { + fn into_address(&self) -> Result { + Ok(*self) + } +} + +impl<'a> IntoAddress for &'a Ipv4Addr { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} + +impl IntoAddress for IpAddr { + fn into_address(&self) -> Result { + match *self { + IpAddr::V4(ref value) => Ok(*value), + + IpAddr::V6(_) => Err(Error::InvalidAddress), + } + } +} + +impl<'a> IntoAddress for &'a IpAddr { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} + +impl IntoAddress for SocketAddrV4 { + fn into_address(&self) -> Result { + Ok(*self.ip()) + } +} + +impl<'a> IntoAddress for &'a SocketAddrV4 { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} + +impl IntoAddress for SocketAddr { + fn into_address(&self) -> Result { + match *self { + SocketAddr::V4(ref value) => Ok(*value.ip()), + + SocketAddr::V6(_) => Err(Error::InvalidAddress), + } + } +} + +impl<'a> IntoAddress for &'a SocketAddr { + fn into_address(&self) -> Result { + (&**self).into_address() + } +} diff --git a/src/async/codec.rs b/src/async/codec.rs new file mode 100644 index 0000000..04ead89 --- /dev/null +++ b/src/async/codec.rs @@ -0,0 +1,149 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io; + +use byteorder::{NativeEndian, NetworkEndian, WriteBytesExt}; +use bytes::{BufMut, Bytes, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +/// A packet protocol IP version +#[derive(Debug)] +enum PacketProtocol { + IPv4, + IPv6, + Other(u8), +} + +// Note: the protocol in the packet information header is platform dependent. +impl PacketProtocol { + #[cfg(any(target_os = "linux", target_os = "android"))] + fn into_pi_field(&self) -> Result { + match self { + PacketProtocol::IPv4 => Ok(libc::ETH_P_IP as u16), + PacketProtocol::IPv6 => Ok(libc::ETH_P_IPV6 as u16), + PacketProtocol::Other(_) => Err(io::Error::new( + io::ErrorKind::Other, + "neither an IPv4 or IPv6 packet", + )), + } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn into_pi_field(&self) -> Result { + match self { + PacketProtocol::IPv4 => Ok(libc::PF_INET as u16), + PacketProtocol::IPv6 => Ok(libc::PF_INET6 as u16), + PacketProtocol::Other(_) => Err(io::Error::new( + io::ErrorKind::Other, + "neither an IPv4 or IPv6 packet", + )), + } + } +} + +/// A Tun Packet to be sent or received on the TUN interface. +#[derive(Debug)] +pub struct TunPacket(PacketProtocol, Bytes); + +/// Infer the protocol based on the first nibble in the packet buffer. +fn infer_proto(buf: &[u8]) -> PacketProtocol { + match buf[0] >> 4 { + 4 => PacketProtocol::IPv4, + 6 => PacketProtocol::IPv6, + p => PacketProtocol::Other(p), + } +} + +impl TunPacket { + /// Create a new `TunPacket` based on a byte slice. + pub fn new(bytes: Vec) -> TunPacket { + let proto = infer_proto(&bytes); + TunPacket(proto, Bytes::from(bytes)) + } + + /// Return this packet's bytes. + pub fn get_bytes(&self) -> &[u8] { + &self.1 + } + + pub fn into_bytes(self) -> Bytes { + self.1 + } +} + +/// A TunPacket Encoder/Decoder. +pub struct TunPacketCodec(bool, i32); + +impl TunPacketCodec { + /// Create a new `TunPacketCodec` specifying whether the underlying + /// tunnel Device has enabled the packet information header. + pub fn new(pi: bool, mtu: i32) -> TunPacketCodec { + TunPacketCodec(pi, mtu) + } +} + +impl Decoder for TunPacketCodec { + type Item = TunPacket; + type Error = io::Error; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + if buf.is_empty() { + return Ok(None); + } + + let mut pkt = buf.split_to(buf.len()); + + // reserve enough space for the next packet + if self.0 { + buf.reserve(self.1 as usize + 4); + } else { + buf.reserve(self.1 as usize); + } + + // if the packet information is enabled we have to ignore the first 4 bytes + if self.0 { + let _ = pkt.split_to(4); + } + + let proto = infer_proto(pkt.as_ref()); + Ok(Some(TunPacket(proto, pkt.freeze()))) + } +} + +impl Encoder for TunPacketCodec { + type Error = io::Error; + + fn encode(&mut self, item: TunPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { + dst.reserve(item.get_bytes().len() + 4); + match item { + TunPacket(proto, bytes) if self.0 => { + // build the packet information header comprising of 2 u16 + // fields: flags and protocol. + let mut buf = Vec::::with_capacity(4); + + // flags is always 0 + buf.write_u16::(0).unwrap(); + // write the protocol as network byte order + buf.write_u16::(proto.into_pi_field()?) + .unwrap(); + + dst.put_slice(&buf); + dst.put(bytes); + } + TunPacket(_, bytes) => dst.put(bytes), + } + Ok(()) + } +} diff --git a/src/async/device.rs b/src/async/device.rs new file mode 100644 index 0000000..5ac89fd --- /dev/null +++ b/src/async/device.rs @@ -0,0 +1,201 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io; +use std::io::{IoSlice, Read, Write}; + +use core::pin::Pin; +use core::task::{Context, Poll}; +use futures_core::ready; +use tokio::io::unix::AsyncFd; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio_util::codec::Framed; + +use crate::device::Device as D; +use crate::platform::{Device, Queue}; +use crate::r#async::codec::*; + +/// An async TUN device wrapper around a TUN device. +pub struct AsyncDevice { + inner: AsyncFd, +} + +impl AsyncDevice { + /// Create a new `AsyncDevice` wrapping around a `Device`. + pub fn new(device: Device) -> io::Result { + device.set_nonblock()?; + Ok(AsyncDevice { + inner: AsyncFd::new(device)?, + }) + } + /// Returns a shared reference to the underlying Device object + pub fn get_ref(&self) -> &Device { + self.inner.get_ref() + } + + /// Returns a mutable reference to the underlying Device object + pub fn get_mut(&mut self) -> &mut Device { + self.inner.get_mut() + } + + /// Consumes this AsyncDevice and return a Framed object (unified Stream and Sink interface) + pub fn into_framed(mut self) -> Framed { + let pi = self.get_mut().has_packet_information(); + let codec = TunPacketCodec::new(pi, self.inner.get_ref().mtu().unwrap_or(1504)); + Framed::new(self, codec) + } +} + +impl AsyncRead for AsyncDevice { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf, + ) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?; + let rbuf = buf.initialize_unfilled(); + match guard.try_io(|inner| inner.get_mut().read(rbuf)) { + Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))), + Err(_wb) => continue, + } + } + } +} + +impl AsyncWrite for AsyncDevice { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?; + match guard.try_io(|inner| inner.get_mut().write(buf)) { + Ok(res) => return Poll::Ready(res), + Err(_wb) => continue, + } + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?; + match guard.try_io(|inner| inner.get_mut().flush()) { + Ok(res) => return Poll::Ready(res), + Err(_wb) => continue, + } + } + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?; + match guard.try_io(|inner| inner.get_mut().write_vectored(bufs)) { + Ok(res) => return Poll::Ready(res), + Err(_wb) => continue, + } + } + } + + fn is_write_vectored(&self) -> bool { + true + } +} + +/// An async TUN device queue wrapper around a TUN device queue. +pub struct AsyncQueue { + inner: AsyncFd, +} + +impl AsyncQueue { + /// Create a new `AsyncQueue` wrapping around a `Queue`. + pub fn new(queue: Queue) -> io::Result { + queue.set_nonblock()?; + Ok(AsyncQueue { + inner: AsyncFd::new(queue)?, + }) + } + /// Returns a shared reference to the underlying Queue object + pub fn get_ref(&self) -> &Queue { + self.inner.get_ref() + } + + /// Returns a mutable reference to the underlying Queue object + pub fn get_mut(&mut self) -> &mut Queue { + self.inner.get_mut() + } + + /// Consumes this AsyncQueue and return a Framed object (unified Stream and Sink interface) + pub fn into_framed(mut self) -> Framed { + let pi = self.get_mut().has_packet_information(); + let codec = TunPacketCodec::new(pi, 1504); + Framed::new(self, codec) + } +} + +impl AsyncRead for AsyncQueue { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf, + ) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_read_ready_mut(cx))?; + let rbuf = buf.initialize_unfilled(); + match guard.try_io(|inner| inner.get_mut().read(rbuf)) { + Ok(res) => return Poll::Ready(res.map(|n| buf.advance(n))), + Err(_wb) => continue, + } + } + } +} + +impl AsyncWrite for AsyncQueue { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?; + match guard.try_io(|inner| inner.get_mut().write(buf)) { + Ok(res) => return Poll::Ready(res), + Err(_wb) => continue, + } + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + let mut guard = ready!(self.inner.poll_write_ready_mut(cx))?; + match guard.try_io(|inner| inner.get_mut().flush()) { + Ok(res) => return Poll::Ready(res), + Err(_wb) => continue, + } + } + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/src/async/mod.rs b/src/async/mod.rs new file mode 100644 index 0000000..b9be373 --- /dev/null +++ b/src/async/mod.rs @@ -0,0 +1,32 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Async specific modules. + +use crate::error; + +use crate::configuration::Configuration; +use crate::platform::create; + +mod device; +pub use self::device::{AsyncDevice, AsyncQueue}; + +mod codec; +pub use self::codec::{TunPacket, TunPacketCodec}; + +/// Create a TUN device with the given name. +pub fn create_as_async(configuration: &Configuration) -> Result { + let device = create(&configuration)?; + AsyncDevice::new(device).map_err(|err| err.into()) +} diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..6c6652c --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,126 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::net::Ipv4Addr; +use std::os::unix::io::RawFd; + +use crate::address::IntoAddress; +use crate::platform; + +/// TUN interface OSI layer of operation. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Layer { + L2, + L3, +} + +impl Default for Layer { + fn default() -> Self { + Layer::L3 + } +} + +/// Configuration builder for a TUN interface. +#[derive(Clone, Default, Debug)] +pub struct Configuration { + pub(crate) name: Option, + pub(crate) platform: platform::Configuration, + + pub(crate) address: Option, + pub(crate) destination: Option, + pub(crate) broadcast: Option, + pub(crate) netmask: Option, + pub(crate) mtu: Option, + pub(crate) enabled: Option, + pub(crate) layer: Option, + pub(crate) queues: Option, + pub(crate) raw_fd: Option, +} + +impl Configuration { + /// Access the platform dependant configuration. + pub fn platform(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut platform::Configuration), + { + f(&mut self.platform); + self + } + + /// Set the name. + pub fn name>(&mut self, name: S) -> &mut Self { + self.name = Some(name.as_ref().into()); + self + } + + /// Set the address. + pub fn address(&mut self, value: A) -> &mut Self { + self.address = Some(value.into_address().unwrap()); + self + } + + /// Set the destination address. + pub fn destination(&mut self, value: A) -> &mut Self { + self.destination = Some(value.into_address().unwrap()); + self + } + + /// Set the broadcast address. + pub fn broadcast(&mut self, value: A) -> &mut Self { + self.broadcast = Some(value.into_address().unwrap()); + self + } + + /// Set the netmask. + pub fn netmask(&mut self, value: A) -> &mut Self { + self.netmask = Some(value.into_address().unwrap()); + self + } + + /// Set the MTU. + pub fn mtu(&mut self, value: i32) -> &mut Self { + self.mtu = Some(value); + self + } + + /// Set the interface to be enabled once created. + pub fn up(&mut self) -> &mut Self { + self.enabled = Some(true); + self + } + + /// Set the interface to be disabled once created. + pub fn down(&mut self) -> &mut Self { + self.enabled = Some(false); + self + } + + /// Set the OSI layer of operation. + pub fn layer(&mut self, value: Layer) -> &mut Self { + self.layer = Some(value); + self + } + + /// Set the number of queues. + pub fn queues(&mut self, value: usize) -> &mut Self { + self.queues = Some(value); + self + } + + /// Set the raw fd. + pub fn raw_fd(&mut self, fd: RawFd) -> &mut Self { + self.raw_fd = Some(fd); + self + } +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..cab8432 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,95 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io::{Read, Write}; +use std::net::Ipv4Addr; + +use crate::configuration::Configuration; +use crate::error::*; + +/// A TUN device. +pub trait Device: Read + Write { + type Queue: Read + Write; + + /// Reconfigure the device. + fn configure(&mut self, config: &Configuration) -> Result<()> { + if let Some(ip) = config.address { + self.set_address(ip)?; + } + + if let Some(ip) = config.destination { + self.set_destination(ip)?; + } + + if let Some(ip) = config.broadcast { + self.set_broadcast(ip)?; + } + + if let Some(ip) = config.netmask { + self.set_netmask(ip)?; + } + + if let Some(mtu) = config.mtu { + self.set_mtu(mtu)?; + } + + if let Some(enabled) = config.enabled { + self.enabled(enabled)?; + } + + Ok(()) + } + + /// Get the device name. + fn name(&self) -> &str; + + /// Set the device name. + fn set_name(&mut self, name: &str) -> Result<()>; + + /// Turn on or off the interface. + fn enabled(&mut self, value: bool) -> Result<()>; + + /// Get the address. + fn address(&self) -> Result; + + /// Set the address. + fn set_address(&mut self, value: Ipv4Addr) -> Result<()>; + + /// Get the destination address. + fn destination(&self) -> Result; + + /// Set the destination address. + fn set_destination(&mut self, value: Ipv4Addr) -> Result<()>; + + /// Get the broadcast address. + fn broadcast(&self) -> Result; + + /// Set the broadcast address. + fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()>; + + /// Get the netmask. + fn netmask(&self) -> Result; + + /// Set the netmask. + fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()>; + + /// Get the MTU. + fn mtu(&self) -> Result; + + /// Set the MTU. + fn set_mtu(&mut self, value: i32) -> Result<()>; + + /// Get a device queue. + fn queue(&mut self, index: usize) -> Option<&mut Self::Queue>; +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..fb41960 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,54 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::{ffi, io, num}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("invalid configuration")] + InvalidConfig, + + #[error("not implementated")] + NotImplemented, + + #[error("device name too long")] + NameTooLong, + + #[error("invalid device name")] + InvalidName, + + #[error("invalid address")] + InvalidAddress, + + #[error("invalid file descriptor")] + InvalidDescriptor, + + #[error("unsuported network layer of operation")] + UnsupportedLayer, + + #[error("invalid queues number")] + InvalidQueuesNumber, + + #[error(transparent)] + Io(#[from] io::Error), + + #[error(transparent)] + Nul(#[from] ffi::NulError), + + #[error(transparent)] + ParseNum(#[from] num::ParseIntError), +} + +pub type Result = ::std::result::Result; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3cb65c2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,53 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +mod error; +pub use crate::error::*; + +mod address; +pub use crate::address::IntoAddress; + +mod device; +pub use crate::device::Device; + +mod configuration; +pub use crate::configuration::{Configuration, Layer}; + +pub mod platform; +pub use crate::platform::create; + +#[cfg(all( + feature = "async", + any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "android" + ) +))] +pub mod r#async; +#[cfg(all( + feature = "async", + any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "android" + ) +))] +pub use r#async::*; + +pub fn configure() -> Configuration { + Configuration::default() +} diff --git a/src/platform/android/device.rs b/src/platform/android/device.rs new file mode 100644 index 0000000..5dd2c80 --- /dev/null +++ b/src/platform/android/device.rs @@ -0,0 +1,214 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. +#![allow(unused_variables)] + +use std::io::{self, Read, Write}; +use std::net::Ipv4Addr; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::sync::Arc; + +use crate::configuration::Configuration; +use crate::device::Device as D; +use crate::error::*; +use crate::platform::posix::{self, Fd}; + +/// A TUN device for Android. +pub struct Device { + queue: Queue, +} + +impl Device { + /// Create a new `Device` for the given `Configuration`. + pub fn new(config: &Configuration) -> Result { + let fd = match config.raw_fd { + Some(raw_fd) => raw_fd, + _ => return Err(Error::InvalidConfig), + }; + let device = { + let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?; + + Device { + queue: Queue { tun: tun }, + } + }; + Ok(device) + } + + /// Split the interface into a `Reader` and `Writer`. + pub fn split(self) -> (posix::Reader, posix::Writer) { + let fd = Arc::new(self.queue.tun); + (posix::Reader(fd.clone()), posix::Writer(fd.clone())) + } + + /// Return whether the device has packet information + pub fn has_packet_information(&self) -> bool { + self.queue.has_packet_information() + } + + /// Set non-blocking mode + pub fn set_nonblock(&self) -> io::Result<()> { + self.queue.set_nonblock() + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.queue.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.queue.tun.read_vectored(bufs) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.queue.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.queue.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.queue.tun.write_vectored(bufs) + } +} + +impl D for Device { + type Queue = Queue; + + fn name(&self) -> &str { + return ""; + } + + fn set_name(&mut self, value: &str) -> Result<()> { + Err(Error::NotImplemented) + } + + fn enabled(&mut self, value: bool) -> Result<()> { + Ok(()) + } + + fn address(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_address(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn destination(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn broadcast(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn netmask(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn mtu(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_mtu(&mut self, value: i32) -> Result<()> { + Ok(()) + } + + fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> { + if index > 0 { + return None; + } + + Some(&mut self.queue) + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.queue.as_raw_fd() + } +} + +impl IntoRawFd for Device { + fn into_raw_fd(self) -> RawFd { + self.queue.into_raw_fd() + } +} + +pub struct Queue { + tun: Fd, +} + +impl Queue { + pub fn has_packet_information(&self) -> bool { + // on Android this is always the case + false + } + + pub fn set_nonblock(&self) -> io::Result<()> { + self.tun.set_nonblock() + } +} + +impl AsRawFd for Queue { + fn as_raw_fd(&self) -> RawFd { + self.tun.as_raw_fd() + } +} + +impl IntoRawFd for Queue { + fn into_raw_fd(self) -> RawFd { + self.tun.into_raw_fd() + } +} + +impl Read for Queue { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.tun.read_vectored(bufs) + } +} + +impl Write for Queue { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.tun.write_vectored(bufs) + } +} diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs new file mode 100644 index 0000000..923e14f --- /dev/null +++ b/src/platform/android/mod.rs @@ -0,0 +1,30 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Android specific functionality. + +mod device; +pub use self::device::{Device, Queue}; + +use crate::configuration::Configuration as C; +use crate::error::*; + +/// Android-only interface configuration. +#[derive(Copy, Clone, Default, Debug)] +pub struct Configuration {} + +/// Create a TUN device with the given name. +pub fn create(configuration: &C) -> Result { + Device::new(&configuration) +} diff --git a/src/platform/ios/device.rs b/src/platform/ios/device.rs new file mode 100644 index 0000000..b079d42 --- /dev/null +++ b/src/platform/ios/device.rs @@ -0,0 +1,214 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. +#![allow(unused_variables)] + +use std::io::{self, Read, Write}; +use std::net::Ipv4Addr; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::sync::Arc; + +use crate::configuration::Configuration; +use crate::device::Device as D; +use crate::error::*; +use crate::platform::posix::{self, Fd}; + +/// A TUN device for iOS. +pub struct Device { + queue: Queue, +} + +impl Device { + /// Create a new `Device` for the given `Configuration`. + pub fn new(config: &Configuration) -> Result { + let fd = match config.raw_fd { + Some(raw_fd) => raw_fd, + _ => return Err(Error::InvalidConfig), + }; + let mut device = unsafe { + let tun = Fd::new(fd).map_err(|_| io::Error::last_os_error())?; + + Device { + queue: Queue { tun: tun }, + } + }; + Ok(device) + } + + /// Split the interface into a `Reader` and `Writer`. + pub fn split(self) -> (posix::Reader, posix::Writer) { + let fd = Arc::new(self.queue.tun); + (posix::Reader(fd.clone()), posix::Writer(fd.clone())) + } + + /// Return whether the device has packet information + pub fn has_packet_information(&self) -> bool { + self.queue.has_packet_information() + } + + /// Set non-blocking mode + pub fn set_nonblock(&self) -> io::Result<()> { + self.queue.set_nonblock() + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.queue.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.queue.tun.read_vectored(bufs) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.queue.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.queue.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.queue.tun.write_vectored(bufs) + } +} + +impl D for Device { + type Queue = Queue; + + fn name(&self) -> &str { + return ""; + } + + fn set_name(&mut self, value: &str) -> Result<()> { + Err(Error::NotImplemented) + } + + fn enabled(&mut self, value: bool) -> Result<()> { + Ok(()) + } + + fn address(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_address(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn destination(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn broadcast(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn netmask(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> { + Ok(()) + } + + fn mtu(&self) -> Result { + Err(Error::NotImplemented) + } + + fn set_mtu(&mut self, value: i32) -> Result<()> { + Ok(()) + } + + fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> { + if index > 0 { + return None; + } + + Some(&mut self.queue) + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.queue.as_raw_fd() + } +} + +impl IntoRawFd for Device { + fn into_raw_fd(self) -> RawFd { + self.queue.into_raw_fd() + } +} + +pub struct Queue { + tun: Fd, +} + +impl Queue { + pub fn has_packet_information(&self) -> bool { + // on ios this is always the case + true + } + + pub fn set_nonblock(&self) -> io::Result<()> { + self.tun.set_nonblock() + } +} + +impl AsRawFd for Queue { + fn as_raw_fd(&self) -> RawFd { + self.tun.as_raw_fd() + } +} + +impl IntoRawFd for Queue { + fn into_raw_fd(self) -> RawFd { + self.tun.into_raw_fd() + } +} + +impl Read for Queue { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.tun.read_vectored(bufs) + } +} + +impl Write for Queue { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.tun.write_vectored(bufs) + } +} diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs new file mode 100644 index 0000000..ea46855 --- /dev/null +++ b/src/platform/ios/mod.rs @@ -0,0 +1,30 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! iOS specific functionality. + +mod device; +pub use self::device::{Device, Queue}; + +use crate::configuration::Configuration as C; +use crate::error::*; + +/// iOS-only interface configuration. +#[derive(Copy, Clone, Default, Debug)] +pub struct Configuration {} + +/// Create a TUN device with the given name. +pub fn create(configuration: &C) -> Result { + Device::new(&configuration) +} diff --git a/src/platform/linux/device.rs b/src/platform/linux/device.rs new file mode 100644 index 0000000..e9b14c0 --- /dev/null +++ b/src/platform/linux/device.rs @@ -0,0 +1,452 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::ffi::{CStr, CString}; +use std::io::{self, Read, Write}; +use std::mem; +use std::net::Ipv4Addr; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::ptr; +use std::vec::Vec; + +use libc; +use libc::{c_char, c_short}; +use libc::{AF_INET, O_RDWR, SOCK_DGRAM}; + +use crate::configuration::{Configuration, Layer}; +use crate::device::Device as D; +use crate::error::*; +use crate::platform::linux::sys::*; +use crate::platform::posix::{Fd, SockAddr}; + +/// A TUN device using the TUN/TAP Linux driver. +pub struct Device { + name: String, + queues: Vec, + ctl: Fd, +} + +impl Device { + /// Create a new `Device` for the given `Configuration`. + pub fn new(config: &Configuration) -> Result { + let mut device = unsafe { + let dev = match config.name.as_ref() { + Some(name) => { + let name = CString::new(name.clone())?; + + if name.as_bytes_with_nul().len() > IFNAMSIZ { + return Err(Error::NameTooLong); + } + + Some(name) + } + + None => None, + }; + + let mut queues = Vec::new(); + + let mut req: ifreq = mem::zeroed(); + + if let Some(dev) = dev.as_ref() { + ptr::copy_nonoverlapping( + dev.as_ptr() as *const c_char, + req.ifrn.name.as_mut_ptr(), + dev.as_bytes().len(), + ); + } + + let device_type: c_short = config.layer.unwrap_or(Layer::L3).into(); + + let queues_num = config.queues.unwrap_or(1); + if queues_num < 1 { + return Err(Error::InvalidQueuesNumber); + } + + req.ifru.flags = device_type + | if config.platform.packet_information { + 0 + } else { + IFF_NO_PI + } + | if queues_num > 1 { IFF_MULTI_QUEUE } else { 0 }; + + for _ in 0..queues_num { + let tun = Fd::new(libc::open(b"/dev/net/tun\0".as_ptr() as *const _, O_RDWR)) + .map_err(|_| io::Error::last_os_error())?; + + if tunsetiff(tun.0, &mut req as *mut _ as *mut _) < 0 { + return Err(io::Error::last_os_error().into()); + } + + queues.push(Queue { + tun, + pi_enabled: config.platform.packet_information, + }); + } + + let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0)) + .map_err(|_| io::Error::last_os_error())?; + + Device { + name: CStr::from_ptr(req.ifrn.name.as_ptr()) + .to_string_lossy() + .into(), + queues, + ctl, + } + }; + + device.configure(config)?; + + Ok(device) + } + + /// Prepare a new request. + unsafe fn request(&self) -> ifreq { + let mut req: ifreq = mem::zeroed(); + ptr::copy_nonoverlapping( + self.name.as_ptr() as *const c_char, + req.ifrn.name.as_mut_ptr(), + self.name.len(), + ); + + req + } + + /// Make the device persistent. + pub fn persist(&mut self) -> Result<()> { + unsafe { + if tunsetpersist(self.as_raw_fd(), &1) < 0 { + Err(io::Error::last_os_error().into()) + } else { + Ok(()) + } + } + } + + /// Set the owner of the device. + pub fn user(&mut self, value: i32) -> Result<()> { + unsafe { + if tunsetowner(self.as_raw_fd(), &value) < 0 { + Err(io::Error::last_os_error().into()) + } else { + Ok(()) + } + } + } + + /// Set the group of the device. + pub fn group(&mut self, value: i32) -> Result<()> { + unsafe { + if tunsetgroup(self.as_raw_fd(), &value) < 0 { + Err(io::Error::last_os_error().into()) + } else { + Ok(()) + } + } + } + + /// Return whether the device has packet information + pub fn has_packet_information(&mut self) -> bool { + self.queues[0].has_packet_information() + } + + /// Set non-blocking mode + pub fn set_nonblock(&self) -> io::Result<()> { + self.queues[0].set_nonblock() + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.queues[0].read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.queues[0].read_vectored(bufs) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.queues[0].write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.queues[0].flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.queues[0].write_vectored(bufs) + } +} + +impl D for Device { + type Queue = Queue; + + fn name(&self) -> &str { + &self.name + } + + fn set_name(&mut self, value: &str) -> Result<()> { + unsafe { + let name = CString::new(value)?; + + if name.as_bytes_with_nul().len() > IFNAMSIZ { + return Err(Error::NameTooLong); + } + + let mut req = self.request(); + ptr::copy_nonoverlapping( + name.as_ptr() as *const c_char, + req.ifru.newname.as_mut_ptr(), + value.len(), + ); + + if siocsifname(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + self.name = value.into(); + + Ok(()) + } + } + + fn enabled(&mut self, value: bool) -> Result<()> { + unsafe { + let mut req = self.request(); + + if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + if value { + req.ifru.flags |= IFF_UP | IFF_RUNNING; + } else { + req.ifru.flags &= !IFF_UP; + } + + if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn address(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.addr).map(Into::into) + } + } + + fn set_address(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.addr = SockAddr::from(value).into(); + + if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn destination(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.dstaddr).map(Into::into) + } + } + + fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.dstaddr = SockAddr::from(value).into(); + + if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn broadcast(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.broadaddr).map(Into::into) + } + } + + fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.broadaddr = SockAddr::from(value).into(); + + if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn netmask(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.netmask).map(Into::into) + } + } + + fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.netmask = SockAddr::from(value).into(); + + if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn mtu(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(req.ifru.mtu) + } + } + + fn set_mtu(&mut self, value: i32) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.mtu = value; + + if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> { + self.queues.get_mut(index) + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.queues[0].as_raw_fd() + } +} + +impl IntoRawFd for Device { + fn into_raw_fd(mut self) -> RawFd { + // It is Ok to swap the first queue with the last one, because the self will be dropped afterwards + let queue = self.queues.swap_remove(0); + queue.into_raw_fd() + } +} + +pub struct Queue { + tun: Fd, + pi_enabled: bool, +} + +impl Queue { + pub fn has_packet_information(&mut self) -> bool { + self.pi_enabled + } + + pub fn set_nonblock(&self) -> io::Result<()> { + self.tun.set_nonblock() + } +} + +impl Read for Queue { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.tun.read_vectored(bufs) + } +} + +impl Write for Queue { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.tun.write_vectored(bufs) + } +} + +impl AsRawFd for Queue { + fn as_raw_fd(&self) -> RawFd { + self.tun.as_raw_fd() + } +} + +impl IntoRawFd for Queue { + fn into_raw_fd(self) -> RawFd { + self.tun.into_raw_fd() + } +} + +impl From for c_short { + fn from(layer: Layer) -> Self { + match layer { + Layer::L2 => IFF_TAP, + Layer::L3 => IFF_TUN, + } + } +} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs new file mode 100644 index 0000000..455a279 --- /dev/null +++ b/src/platform/linux/mod.rs @@ -0,0 +1,43 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Linux specific functionality. + +pub mod sys; + +mod device; +pub use self::device::{Device, Queue}; + +use crate::configuration::Configuration as C; +use crate::error::*; + +/// Linux-only interface configuration. +#[derive(Copy, Clone, Default, Debug)] +pub struct Configuration { + pub(crate) packet_information: bool, +} + +impl Configuration { + /// Enable or disable packet information, when enabled the first 4 bytes of + /// each packet is a header with flags and protocol type. + pub fn packet_information(&mut self, value: bool) -> &mut Self { + self.packet_information = value; + self + } +} + +/// Create a TUN device with the given name. +pub fn create(configuration: &C) -> Result { + Device::new(configuration) +} diff --git a/src/platform/linux/sys.rs b/src/platform/linux/sys.rs new file mode 100644 index 0000000..cf35c93 --- /dev/null +++ b/src/platform/linux/sys.rs @@ -0,0 +1,111 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Bindings to internal Linux stuff. + +use ioctl::*; +use libc::sockaddr; +use libc::{c_char, c_int, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void}; + +pub const IFNAMSIZ: usize = 16; + +pub const IFF_UP: c_short = 0x1; +pub const IFF_RUNNING: c_short = 0x40; + +pub const IFF_TUN: c_short = 0x0001; +pub const IFF_TAP: c_short = 0x0002; +pub const IFF_NO_PI: c_short = 0x1000; +pub const IFF_MULTI_QUEUE: c_short = 0x0100; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifmap { + pub mem_start: c_ulong, + pub mem_end: c_ulong, + pub base_addr: c_ushort, + pub irq: c_uchar, + pub dma: c_uchar, + pub port: c_uchar, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifsu { + pub raw_hdlc_proto: *mut c_void, + pub cisco: *mut c_void, + pub fr: *mut c_void, + pub fr_pvc: *mut c_void, + pub fr_pvc_info: *mut c_void, + pub sync: *mut c_void, + pub te1: *mut c_void, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct if_settings { + pub type_: c_uint, + pub size: c_uint, + pub ifsu: ifsu, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifrn { + pub name: [c_char; IFNAMSIZ], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifru { + pub addr: sockaddr, + pub dstaddr: sockaddr, + pub broadaddr: sockaddr, + pub netmask: sockaddr, + pub hwaddr: sockaddr, + + pub flags: c_short, + pub ivalue: c_int, + pub mtu: c_int, + pub map: ifmap, + pub slave: [c_char; IFNAMSIZ], + pub newname: [c_char; IFNAMSIZ], + pub data: *mut c_void, + pub settings: if_settings, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifreq { + pub ifrn: ifrn, + pub ifru: ifru, +} + +ioctl!(bad read siocgifflags with 0x8913; ifreq); +ioctl!(bad write siocsifflags with 0x8914; ifreq); +ioctl!(bad read siocgifaddr with 0x8915; ifreq); +ioctl!(bad write siocsifaddr with 0x8916; ifreq); +ioctl!(bad read siocgifdstaddr with 0x8917; ifreq); +ioctl!(bad write siocsifdstaddr with 0x8918; ifreq); +ioctl!(bad read siocgifbrdaddr with 0x8919; ifreq); +ioctl!(bad write siocsifbrdaddr with 0x891a; ifreq); +ioctl!(bad read siocgifnetmask with 0x891b; ifreq); +ioctl!(bad write siocsifnetmask with 0x891c; ifreq); +ioctl!(bad read siocgifmtu with 0x8921; ifreq); +ioctl!(bad write siocsifmtu with 0x8922; ifreq); +ioctl!(bad write siocsifname with 0x8923; ifreq); + +ioctl!(write tunsetiff with b'T', 202; c_int); +ioctl!(write tunsetpersist with b'T', 203; c_int); +ioctl!(write tunsetowner with b'T', 204; c_int); +ioctl!(write tunsetgroup with b'T', 206; c_int); diff --git a/src/platform/macos/device.rs b/src/platform/macos/device.rs new file mode 100644 index 0000000..879e1ad --- /dev/null +++ b/src/platform/macos/device.rs @@ -0,0 +1,438 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. +#![allow(unused_variables)] + +use std::ffi::CStr; +use std::io::{self, Read, Write}; +use std::mem; +use std::net::Ipv4Addr; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::ptr; +use std::sync::Arc; + +use libc; +use libc::{c_char, c_uint, c_void, sockaddr, socklen_t, AF_INET, SOCK_DGRAM}; + +use crate::configuration::{Configuration, Layer}; +use crate::device::Device as D; +use crate::error::*; +use crate::platform::macos::sys::*; +use crate::platform::posix::{self, Fd, SockAddr}; + +/// A TUN device using the TUN macOS driver. +pub struct Device { + name: String, + queue: Queue, + ctl: Fd, +} + +impl Device { + /// Create a new `Device` for the given `Configuration`. + pub fn new(config: &Configuration) -> Result { + let id = if let Some(name) = config.name.as_ref() { + if name.len() > IFNAMSIZ { + return Err(Error::NameTooLong); + } + + if !name.starts_with("utun") { + return Err(Error::InvalidName); + } + + name[4..].parse()? + } else { + 0 + }; + + if config.layer.filter(|l| *l != Layer::L3).is_some() { + return Err(Error::UnsupportedLayer); + } + + let queues_number = config.queues.unwrap_or(1); + if queues_number != 1 { + return Err(Error::InvalidQueuesNumber); + } + + let mut device = unsafe { + let tun = Fd::new(libc::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL)) + .map_err(|_| io::Error::last_os_error())?; + + let mut info = ctl_info { + ctl_id: 0, + ctl_name: { + let mut buffer = [0; 96]; + for (i, o) in UTUN_CONTROL_NAME.as_bytes().iter().zip(buffer.iter_mut()) { + *o = *i as _; + } + buffer + }, + }; + + if ctliocginfo(tun.0, &mut info as *mut _ as *mut _) < 0 { + return Err(io::Error::last_os_error().into()); + } + + let addr = sockaddr_ctl { + sc_id: info.ctl_id, + sc_len: mem::size_of::() as _, + sc_family: AF_SYSTEM, + ss_sysaddr: AF_SYS_CONTROL, + sc_unit: id as c_uint, + sc_reserved: [0; 5], + }; + + if libc::connect( + tun.0, + &addr as *const sockaddr_ctl as *const sockaddr, + mem::size_of_val(&addr) as socklen_t, + ) < 0 + { + return Err(io::Error::last_os_error().into()); + } + + let mut name = [0u8; 64]; + let mut name_len: socklen_t = 64; + + if libc::getsockopt( + tun.0, + SYSPROTO_CONTROL, + UTUN_OPT_IFNAME, + &mut name as *mut _ as *mut c_void, + &mut name_len as *mut socklen_t, + ) < 0 + { + return Err(io::Error::last_os_error().into()); + } + + let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0)) + .map_err(|_| io::Error::last_os_error())?; + + Device { + name: CStr::from_ptr(name.as_ptr() as *const c_char) + .to_string_lossy() + .into(), + queue: Queue { tun: tun }, + ctl: ctl, + } + }; + + device.configure(&config)?; + + Ok(device) + } + + /// Prepare a new request. + pub unsafe fn request(&self) -> ifreq { + let mut req: ifreq = mem::zeroed(); + ptr::copy_nonoverlapping( + self.name.as_ptr() as *const c_char, + req.ifrn.name.as_mut_ptr(), + self.name.len(), + ); + + req + } + + /// Set the IPv4 alias of the device. + pub fn set_alias(&mut self, addr: Ipv4Addr, broadaddr: Ipv4Addr, mask: Ipv4Addr) -> Result<()> { + unsafe { + let mut req: ifaliasreq = mem::zeroed(); + ptr::copy_nonoverlapping( + self.name.as_ptr() as *const c_char, + req.ifran.as_mut_ptr(), + self.name.len(), + ); + + req.addr = SockAddr::from(addr).into(); + req.broadaddr = SockAddr::from(broadaddr).into(); + req.mask = SockAddr::from(mask).into(); + + if siocaifaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + /// Split the interface into a `Reader` and `Writer`. + pub fn split(self) -> (posix::Reader, posix::Writer) { + let fd = Arc::new(self.queue.tun); + (posix::Reader(fd.clone()), posix::Writer(fd.clone())) + } + + /// Return whether the device has packet information + pub fn has_packet_information(&self) -> bool { + self.queue.has_packet_information() + } + + /// Set non-blocking mode + pub fn set_nonblock(&self) -> io::Result<()> { + self.queue.set_nonblock() + } +} + +impl Read for Device { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.queue.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.queue.tun.read_vectored(bufs) + } +} + +impl Write for Device { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.queue.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.queue.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.queue.tun.write_vectored(bufs) + } +} + +impl D for Device { + type Queue = Queue; + + fn name(&self) -> &str { + &self.name + } + + // XXX: Cannot set interface name on Darwin. + fn set_name(&mut self, value: &str) -> Result<()> { + Err(Error::InvalidName) + } + + fn enabled(&mut self, value: bool) -> Result<()> { + unsafe { + let mut req = self.request(); + + if siocgifflags(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + if value { + req.ifru.flags |= IFF_UP | IFF_RUNNING; + } else { + req.ifru.flags &= !IFF_UP; + } + + if siocsifflags(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn address(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.addr).map(Into::into) + } + } + + fn set_address(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.addr = SockAddr::from(value).into(); + + if siocsifaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn destination(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.dstaddr).map(Into::into) + } + } + + fn set_destination(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.dstaddr = SockAddr::from(value).into(); + + if siocsifdstaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn broadcast(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::new(&req.ifru.broadaddr).map(Into::into) + } + } + + fn set_broadcast(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.broadaddr = SockAddr::from(value).into(); + + if siocsifbrdaddr(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn netmask(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifnetmask(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + SockAddr::unchecked(&req.ifru.addr).map(Into::into) + } + } + + fn set_netmask(&mut self, value: Ipv4Addr) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.addr = SockAddr::from(value).into(); + + if siocsifnetmask(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn mtu(&self) -> Result { + unsafe { + let mut req = self.request(); + + if siocgifmtu(self.ctl.as_raw_fd(), &mut req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(req.ifru.mtu) + } + } + + fn set_mtu(&mut self, value: i32) -> Result<()> { + unsafe { + let mut req = self.request(); + req.ifru.mtu = value; + + if siocsifmtu(self.ctl.as_raw_fd(), &req) < 0 { + return Err(io::Error::last_os_error().into()); + } + + Ok(()) + } + } + + fn queue(&mut self, index: usize) -> Option<&mut Self::Queue> { + if index > 0 { + return None; + } + + Some(&mut self.queue) + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.queue.as_raw_fd() + } +} + +impl IntoRawFd for Device { + fn into_raw_fd(self) -> RawFd { + self.queue.into_raw_fd() + } +} + +pub struct Queue { + tun: Fd, +} + +impl Queue { + pub fn has_packet_information(&self) -> bool { + // on macos this is always the case + true + } + + pub fn set_nonblock(&self) -> io::Result<()> { + self.tun.set_nonblock() + } +} + +impl AsRawFd for Queue { + fn as_raw_fd(&self) -> RawFd { + self.tun.as_raw_fd() + } +} + +impl IntoRawFd for Queue { + fn into_raw_fd(self) -> RawFd { + self.tun.into_raw_fd() + } +} + +impl Read for Queue { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.tun.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.tun.read_vectored(bufs) + } +} + +impl Write for Queue { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.tun.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.tun.flush() + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.tun.write_vectored(bufs) + } +} diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs new file mode 100644 index 0000000..36c82bb --- /dev/null +++ b/src/platform/macos/mod.rs @@ -0,0 +1,32 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! macOS specific functionality. + +pub mod sys; + +mod device; +pub use self::device::{Device, Queue}; + +use crate::configuration::Configuration as C; +use crate::error::*; + +/// macOS-only interface configuration. +#[derive(Copy, Clone, Default, Debug)] +pub struct Configuration {} + +/// Create a TUN device with the given name. +pub fn create(configuration: &C) -> Result { + Device::new(&configuration) +} diff --git a/src/platform/macos/sys.rs b/src/platform/macos/sys.rs new file mode 100644 index 0000000..0a9ff2f --- /dev/null +++ b/src/platform/macos/sys.rs @@ -0,0 +1,138 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Bindings to internal macOS stuff. + +use ioctl::*; +use libc::sockaddr; +use libc::{c_char, c_int, c_short, c_uint, c_ushort, c_void}; + +pub const IFNAMSIZ: usize = 16; + +pub const IFF_UP: c_short = 0x1; +pub const IFF_RUNNING: c_short = 0x40; + +pub const AF_SYS_CONTROL: c_ushort = 2; +pub const AF_SYSTEM: c_char = 32; +pub const PF_SYSTEM: c_int = AF_SYSTEM as c_int; +pub const SYSPROTO_CONTROL: c_int = 2; +pub const UTUN_OPT_IFNAME: c_int = 2; +pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control"; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ctl_info { + pub ctl_id: c_uint, + pub ctl_name: [c_char; 96], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sockaddr_ctl { + pub sc_len: c_char, + pub sc_family: c_char, + pub ss_sysaddr: c_ushort, + pub sc_id: c_uint, + pub sc_unit: c_uint, + pub sc_reserved: [c_uint; 5], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifrn { + pub name: [c_char; IFNAMSIZ], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifdevmtu { + pub current: c_int, + pub min: c_int, + pub max: c_int, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifku { + pub ptr: *mut c_void, + pub value: c_int, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifkpi { + pub module_id: c_uint, + pub type_: c_uint, + pub ifku: ifku, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifru { + pub addr: sockaddr, + pub dstaddr: sockaddr, + pub broadaddr: sockaddr, + + pub flags: c_short, + pub metric: c_int, + pub mtu: c_int, + pub phys: c_int, + pub media: c_int, + pub intval: c_int, + pub data: *mut c_void, + pub devmtu: ifdevmtu, + pub wake_flags: c_uint, + pub route_refcnt: c_uint, + pub cap: [c_int; 2], + pub functional_type: c_uint, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifreq { + pub ifrn: ifrn, + pub ifru: ifru, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifaliasreq { + pub ifran: [c_char; IFNAMSIZ], + pub addr: sockaddr, + pub broadaddr: sockaddr, + pub mask: sockaddr, +} + +ioctl!(readwrite ctliocginfo with 'N', 3; ctl_info); + +ioctl!(write siocsifflags with 'i', 16; ifreq); +ioctl!(readwrite siocgifflags with 'i', 17; ifreq); + +ioctl!(write siocsifaddr with 'i', 12; ifreq); +ioctl!(readwrite siocgifaddr with 'i', 33; ifreq); + +ioctl!(write siocsifdstaddr with 'i', 14; ifreq); +ioctl!(readwrite siocgifdstaddr with 'i', 34; ifreq); + +ioctl!(write siocsifbrdaddr with 'i', 19; ifreq); +ioctl!(readwrite siocgifbrdaddr with 'i', 35; ifreq); + +ioctl!(write siocsifnetmask with 'i', 22; ifreq); +ioctl!(readwrite siocgifnetmask with 'i', 37; ifreq); + +ioctl!(write siocsifmtu with 'i', 52; ifreq); +ioctl!(readwrite siocgifmtu with 'i', 51; ifreq); + +ioctl!(write siocaifaddr with 'i', 26; ifaliasreq); +ioctl!(write siocdifaddr with 'i', 25; ifreq); diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..2ecf9e1 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,70 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! Platform specific modules. + +#[cfg(unix)] +pub mod posix; + +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_os = "linux")] +pub use self::linux::{create, Configuration, Device, Queue}; + +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "macos")] +pub use self::macos::{create, Configuration, Device, Queue}; + +#[cfg(target_os = "ios")] +pub mod ios; +#[cfg(target_os = "ios")] +pub use self::ios::{create, Configuration, Device, Queue}; + +#[cfg(target_os = "android")] +pub mod android; +#[cfg(target_os = "android")] +pub use self::android::{create, Configuration, Device, Queue}; + +#[cfg(test)] +mod test { + use crate::configuration::Configuration; + use crate::device::Device; + use std::net::Ipv4Addr; + + #[test] + fn create() { + let dev = super::create( + Configuration::default() + .name("utun6") + .address("192.168.50.1") + .netmask("255.255.0.0") + .mtu(1400) + .up(), + ) + .unwrap(); + + assert_eq!( + "192.168.50.1".parse::().unwrap(), + dev.address().unwrap() + ); + + assert_eq!( + "255.255.0.0".parse::().unwrap(), + dev.netmask().unwrap() + ); + + assert_eq!(1400, dev.mtu().unwrap()); + } +} diff --git a/src/platform/posix/fd.rs b/src/platform/posix/fd.rs new file mode 100644 index 0000000..89ea161 --- /dev/null +++ b/src/platform/posix/fd.rs @@ -0,0 +1,124 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io::{self, Read, Write}; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; + +use crate::error::*; +use libc::{self, fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; + +/// POSIX file descriptor support for `io` traits. +pub struct Fd(pub RawFd); + +impl Fd { + pub fn new(value: RawFd) -> Result { + if value < 0 { + return Err(Error::InvalidDescriptor); + } + + Ok(Fd(value)) + } + + /// Enable non-blocking mode + pub fn set_nonblock(&self) -> io::Result<()> { + match unsafe { fcntl(self.0, F_SETFL, fcntl(self.0, F_GETFL) | O_NONBLOCK) } { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()), + } + } +} + +impl Read for Fd { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unsafe { + let amount = libc::read(self.0, buf.as_mut_ptr() as *mut _, buf.len()); + + if amount < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(amount as usize) + } + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + unsafe { + let iov = bufs.as_ptr().cast(); + let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _; + + let n = libc::readv(self.0, iov, iovcnt); + if n < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(n as usize) + } + } +} + +impl Write for Fd { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { + let amount = libc::write(self.0, buf.as_ptr() as *const _, buf.len()); + + if amount < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(amount as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + unsafe { + let iov = bufs.as_ptr().cast(); + let iovcnt = bufs.len().min(libc::c_int::MAX as usize) as _; + + let n = libc::writev(self.0, iov, iovcnt); + if n < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(n as usize) + } + } +} + +impl AsRawFd for Fd { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl IntoRawFd for Fd { + fn into_raw_fd(mut self) -> RawFd { + let fd = self.0; + self.0 = -1; + fd + } +} + +impl Drop for Fd { + fn drop(&mut self) { + unsafe { + if self.0 >= 0 { + libc::close(self.0); + } + } + } +} diff --git a/src/platform/posix/mod.rs b/src/platform/posix/mod.rs new file mode 100644 index 0000000..6b972b5 --- /dev/null +++ b/src/platform/posix/mod.rs @@ -0,0 +1,24 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +//! POSIX compliant support. + +mod sockaddr; +pub use self::sockaddr::SockAddr; + +mod fd; +pub use self::fd::Fd; + +mod split; +pub use self::split::{Reader, Writer}; diff --git a/src/platform/posix/sockaddr.rs b/src/platform/posix/sockaddr.rs new file mode 100644 index 0000000..0eaaaa0 --- /dev/null +++ b/src/platform/posix/sockaddr.rs @@ -0,0 +1,96 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::mem; +use std::net::Ipv4Addr; +use std::ptr; + +#[cfg(any(target_os = "macos", target_os = "ios"))] +use libc::c_uchar; +#[cfg(any(target_os = "linux", target_os = "android"))] +use libc::c_ushort; + +use libc::AF_INET as _AF_INET; +use libc::{in_addr, sockaddr, sockaddr_in}; + +use crate::error::*; + +/// A wrapper for `sockaddr_in`. +#[derive(Copy, Clone)] +pub struct SockAddr(sockaddr_in); + +#[cfg(any(target_os = "linux", target_os = "android"))] +const AF_INET: c_ushort = _AF_INET as c_ushort; + +#[cfg(any(target_os = "macos", target_os = "ios"))] +const AF_INET: c_uchar = _AF_INET as c_uchar; + +impl SockAddr { + /// Create a new `SockAddr` from a generic `sockaddr`. + pub fn new(value: &sockaddr) -> Result { + if value.sa_family != AF_INET { + return Err(Error::InvalidAddress); + } + + unsafe { Self::unchecked(value) } + } + + /// # Safety + /// Create a new `SockAddr` and not check the source. + pub unsafe fn unchecked(value: &sockaddr) -> Result { + Ok(SockAddr(ptr::read(value as *const _ as *const _))) + } + + /// # Safety + /// Get a generic pointer to the `SockAddr`. + pub unsafe fn as_ptr(&self) -> *const sockaddr { + &self.0 as *const _ as *const sockaddr + } +} + +impl From for SockAddr { + fn from(ip: Ipv4Addr) -> SockAddr { + let octets = ip.octets(); + let mut addr = unsafe { mem::zeroed::() }; + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr = in_addr { + s_addr: u32::from_ne_bytes(octets), + }; + + SockAddr(addr) + } +} + +impl From for Ipv4Addr { + fn from(addr: SockAddr) -> Ipv4Addr { + let ip = addr.0.sin_addr.s_addr; + let [a, b, c, d] = ip.to_ne_bytes(); + + Ipv4Addr::new(a, b, c, d) + } +} + +impl From for sockaddr { + fn from(addr: SockAddr) -> sockaddr { + unsafe { mem::transmute(addr.0) } + } +} + +impl From for sockaddr_in { + fn from(addr: SockAddr) -> sockaddr_in { + addr.0 + } +} diff --git a/src/platform/posix/split.rs b/src/platform/posix/split.rs new file mode 100644 index 0000000..7364102 --- /dev/null +++ b/src/platform/posix/split.rs @@ -0,0 +1,105 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyleft (ↄ) meh. | http://meh.schizofreni.co +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +use std::io::{self, Read, Write}; +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; + +use crate::platform::posix::Fd; +use libc; + +/// Read-only end for a file descriptor. +pub struct Reader(pub(crate) Arc); + +/// Write-only end for a file descriptor. +pub struct Writer(pub(crate) Arc); + +impl Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unsafe { + let amount = libc::read(self.0.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len()); + + if amount < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(amount as usize) + } + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + unsafe { + let mut msg: libc::msghdr = mem::zeroed(); + // msg.msg_name: NULL + // msg.msg_namelen: 0 + msg.msg_iov = bufs.as_mut_ptr().cast(); + msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _; + + let n = libc::recvmsg(self.0.as_raw_fd(), &mut msg, 0); + if n < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(n as usize) + } + } +} + +impl Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { + let amount = libc::write(self.0.as_raw_fd(), buf.as_ptr() as *const _, buf.len()); + + if amount < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(amount as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + unsafe { + let mut msg: libc::msghdr = mem::zeroed(); + // msg.msg_name = NULL + // msg.msg_namelen = 0 + msg.msg_iov = bufs.as_ptr() as *mut _; + msg.msg_iovlen = bufs.len().min(libc::c_int::MAX as usize) as _; + + let n = libc::sendmsg(self.0.as_raw_fd(), &msg, 0); + if n < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(n as usize) + } + } +} + +impl AsRawFd for Reader { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl AsRawFd for Writer { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +}