diff --git a/src/platform/macos/device.rs b/src/platform/macos/device.rs index 6af3336c..b3d2d109 100644 --- a/src/platform/macos/device.rs +++ b/src/platform/macos/device.rs @@ -19,18 +19,19 @@ use crate::{ error::*, platform::{ macos::sys::*, - posix::{self, Fd, SockAddr}, + posix::{self, Fd, SockAddr, SockAddrV6}, }, }; use libc::{ - self, c_char, c_short, c_uint, c_void, sockaddr, socklen_t, AF_INET, AF_SYSTEM, AF_SYS_CONTROL, - IFF_RUNNING, IFF_UP, IFNAMSIZ, PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, + self, c_char, c_short, c_uint, c_void, sockaddr, socklen_t, AF_INET, AF_INET6, AF_SYSTEM, + AF_SYS_CONTROL, IFF_RUNNING, IFF_UP, IFNAMSIZ, PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL, + UTUN_OPT_IFNAME, }; use std::{ ffi::CStr, io::{self, Read, Write}, mem, - net::Ipv4Addr, + net::{Ipv4Addr, Ipv6Addr}, os::unix::io::{AsRawFd, IntoRawFd, RawFd}, ptr, sync::Arc, @@ -41,6 +42,7 @@ pub struct Device { name: String, queue: Queue, ctl: Fd, + ctl_inet6: Fd, } impl Device { @@ -111,6 +113,7 @@ impl Device { } let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM, 0))?; + let ctl_inet6 = Fd::new(libc::socket(AF_INET6, SOCK_DGRAM, 0))?; Device { name: CStr::from_ptr(name.as_ptr() as *const c_char) @@ -118,6 +121,7 @@ impl Device { .into(), queue: Queue { tun }, ctl, + ctl_inet6, } }; @@ -166,6 +170,29 @@ impl Device { } } + /// Add IPv6 alias of the device. + /// Tun devices are permitted to have multiple IPv6 aliases. + pub fn add_ipv6_alias(&mut self, addr: Ipv6Addr, mask: Ipv6Addr) -> Result<()> { + unsafe { + let mut req: in6_aliasreq = mem::zeroed(); + ptr::copy_nonoverlapping( + self.name.as_ptr() as *const c_char, + req.name.as_mut_ptr(), + self.name.len(), + ); + req.addr = SockAddrV6::from(addr).into(); + req.prefixmask = SockAddrV6::from(mask).into(); + req.lifetime.ia6t_pltime = u32::MAX; + req.lifetime.ia6t_vltime = u32::MAX; + + if siocaifaddr_in6(self.ctl_inet6.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); diff --git a/src/platform/macos/sys.rs b/src/platform/macos/sys.rs index 94412cdc..d852849f 100644 --- a/src/platform/macos/sys.rs +++ b/src/platform/macos/sys.rs @@ -15,7 +15,9 @@ //! Bindings to internal macOS stuff. use ioctl::*; -use libc::{c_char, c_int, c_short, c_uint, c_ushort, c_void, sockaddr, IFNAMSIZ}; +use libc::{ + c_char, c_int, c_short, c_uint, c_ushort, c_void, sockaddr, sockaddr_in6, time_t, IFNAMSIZ, +}; pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control"; @@ -109,6 +111,28 @@ pub struct ifaliasreq { pub mask: sockaddr, } +#[allow(non_camel_case_types)] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in6_aliasreq { + pub name: [c_char; IFNAMSIZ], + pub addr: sockaddr_in6, + pub dstaddr: sockaddr_in6, + pub prefixmask: sockaddr_in6, + pub flags: c_int, + pub lifetime: in6_addrlifetime, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in6_addrlifetime { + pub ia6t_expire: time_t, + pub ia6t_preferred: time_t, + pub ia6t_vltime: u32, + pub ia6t_pltime: u32, +} + ioctl!(readwrite ctliocginfo with 'N', 3; ctl_info); ioctl!(write siocsifflags with 'i', 16; ifreq); @@ -131,3 +155,5 @@ ioctl!(readwrite siocgifmtu with 'i', 51; ifreq); ioctl!(write siocaifaddr with 'i', 26; ifaliasreq); ioctl!(write siocdifaddr with 'i', 25; ifreq); + +ioctl!(write siocaifaddr_in6 with 'i', 26; in6_aliasreq); diff --git a/src/platform/posix/mod.rs b/src/platform/posix/mod.rs index 6b972b5b..531ffb7b 100644 --- a/src/platform/posix/mod.rs +++ b/src/platform/posix/mod.rs @@ -15,7 +15,7 @@ //! POSIX compliant support. mod sockaddr; -pub use self::sockaddr::SockAddr; +pub use self::sockaddr::{SockAddr, SockAddrV6}; mod fd; pub use self::fd::Fd; diff --git a/src/platform/posix/sockaddr.rs b/src/platform/posix/sockaddr.rs index ccc2a945..161030d9 100644 --- a/src/platform/posix/sockaddr.rs +++ b/src/platform/posix/sockaddr.rs @@ -12,8 +12,12 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use libc::{in_addr, sockaddr, sockaddr_in}; -use std::{mem, net::Ipv4Addr, ptr}; +use libc::{in6_addr, in_addr, sockaddr, sockaddr_in, sockaddr_in6}; +use std::{ + mem, + net::{Ipv4Addr, Ipv6Addr}, + ptr, +}; use crate::error::*; @@ -79,3 +83,57 @@ impl From for sockaddr_in { addr.0 } } + +/// A wrapper for `sockaddr_in6`. +#[derive(Copy, Clone)] +pub struct SockAddrV6(sockaddr_in6); + +impl SockAddrV6 { + /// Create a new `SockAddrV6` from a generic `sockaddr`. + pub fn new(value: &sockaddr_in6) -> Result { + if value.sin6_family != libc::AF_INET6 as libc::sa_family_t { + return Err(Error::InvalidAddress); + } + + unsafe { Self::unchecked(value) } + } + + /// # Safety + /// Create a new `SockAddrV6` and not check the source. + pub unsafe fn unchecked(value: &sockaddr_in6) -> Result { + Ok(SockAddrV6(ptr::read(value as *const _ as *const _))) + } +} + +impl From for SockAddrV6 { + fn from(ip: Ipv6Addr) -> SockAddrV6 { + let mut addr = unsafe { mem::zeroed::() }; + let addr_family = if ip.is_unspecified() { + libc::AF_UNSPEC + } else { + libc::AF_INET6 + }; + + // macos: ioctl does not accept sockaddr_in6 without sin6_len set. + addr.sin6_len = mem::size_of::() as u8; + addr.sin6_family = addr_family as libc::sa_family_t; + addr.sin6_port = 0; + addr.sin6_addr = in6_addr { + s6_addr: ip.octets(), + }; + + SockAddrV6(addr) + } +} + +impl From for Ipv6Addr { + fn from(addr: SockAddrV6) -> Ipv6Addr { + Ipv6Addr::from(addr.0.sin6_addr.s6_addr) + } +} + +impl From for sockaddr_in6 { + fn from(addr: SockAddrV6) -> sockaddr_in6 { + addr.0 + } +}