// GStreamer // // Copyright (C) 2015-2023 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use std::net::Ipv4Addr; use crate::{bail, error::Error}; #[derive(Debug)] /// Network interface information. pub struct InterfaceInfo { /// Name of the interface pub name: String, /// Other name of the interface, if any pub other_name: Option, /// Interface index pub index: usize, /// Unicast IPv4 address of the interface pub ip_addr: Ipv4Addr, /// Physical MAC address of the interface, if any pub hw_addr: Option<[u8; 6]>, } #[cfg(unix)] mod imp { use super::*; use std::{ffi::CStr, io, marker, mem, net::UdpSocket, os::unix::io::AsRawFd, ptr}; use crate::{error::Context, ffi::unix::*}; /// Returns information for all non-loopback, multicast-capable network interfaces. pub fn query_interfaces() -> Result, Error> { struct Ifaddrs(*mut ifaddrs); impl Ifaddrs { fn new() -> io::Result { loop { // SAFETY: Requires passing valid storage for the returned ifaddrs pointer and // returns -1 on errors. It might return NULL if there are no network interfaces. // // On error it might return EINTR in which case we should simply try again. unsafe { let mut ifaddrs = ptr::null_mut(); if getifaddrs(&mut ifaddrs) == -1 { let err = io::Error::last_os_error(); if err.kind() == std::io::ErrorKind::Interrupted { continue; } return Err(err); } else { return Ok(Self(ifaddrs)); } } } } fn iter(&self) -> IfaddrsIter { IfaddrsIter { ptr: ptr::NonNull::new(self.0), phantom: marker::PhantomData, } } } impl Drop for Ifaddrs { fn drop(&mut self) { // SAFETY: The pointer is a valid ifaddrs pointer by construction and dropped only // once, so freeing it here is OK. It might be NULL so check for that first. unsafe { if !self.0.is_null() { freeifaddrs(self.0); } } } } struct IfaddrsIter<'a> { ptr: Option>, phantom: marker::PhantomData<&'a Ifaddrs>, } impl<'a> Iterator for IfaddrsIter<'a> { type Item = &'a ifaddrs; fn next(&mut self) -> Option { match self.ptr { None => None, Some(ptr) => { // SAFETY: The pointer is a valid ifaddrs pointer by construction so // creating a reference to it is OK. let addr = unsafe { &*ptr.as_ptr() }; self.ptr = ptr::NonNull::new(addr.ifa_next); Some(addr) } } } } let ifaddrs = Ifaddrs::new().context("Failed getting interface addresses")?; let mut if_infos = Vec::::new(); for ifaddr in ifaddrs.iter() { // SAFETY: ifa_name points to a NUL-terminated interface name string that is valid as // long as its struct is let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) }.to_str().unwrap(); // Skip loopback interfaces, interfaces that are not up and interfaces that can't do // multicast. These are all unusable for PTP purposes. let flags = ifaddr.ifa_flags; if flags & IFF_LOOPBACK != 0 { debug!("Interface {} is loopback interface", name); continue; } if flags & IFF_UP == 0 { debug!("Interface {} is not up", name); continue; } if flags & IFF_MULTICAST == 0 { debug!("Interface {} does not support multicast", name); continue; } // If the interface has no address then skip it. Only interfaces with IPv4 addresses // are usable for PTP purposes. if ifaddr.ifa_addr.is_null() { debug!("Interface {} has no IPv4 address", name); continue; } // Get the interface index from its name. If it has none then we can't use it to join // the PTP multicast group reliable for this interface. // // SAFETY: Must be called with a valid, NUL-terminated string which is provided by // ifa_name and will return the interface index or zero on error. // // On error it can return EINTR in which case we should try again. let index = loop { let index = unsafe { if_nametoindex(ifaddr.ifa_name) } as usize; if index == 0 { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::Interrupted { continue; } } break index; }; if index == 0 { debug!("Interface {} has no valid interface index", name); continue; } // Interfaces are listed multiple times here, once per address. We collect all IPv4 and // MAC addresses for interfaces below. // SAFETY: ifa_addr is a valid sockaddr pointer and was checked to be not NULL further // above. let sa_family = unsafe { (*ifaddr.ifa_addr).sa_family }; // If this interface has an IPv4 address then retrieve and store it here. if sa_family == AF_INET as _ { // SAFETY: If the address family is AF_INET then it is actually a valid sockaddr_in // pointer and can be used as such. let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_in) }; let ip_addr = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()); debug!("Interface {} has IPv4 address {}", name, ip_addr); if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { if if_info.ip_addr.is_broadcast() { if_info.ip_addr = ip_addr; } } else { if_infos.push(InterfaceInfo { name: String::from(name), other_name: None, index, ip_addr, hw_addr: None, }); } } #[cfg(target_os = "linux")] { if sa_family == AF_PACKET as _ { // SAFETY: If the address family is AF_PACKET then it is actually a valid sockaddr_ll // pointer and can be used as such. let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_ll) }; if addr.sll_halen == 6 { let mut hw_addr = [0u8; 6]; hw_addr.copy_from_slice(&addr.sll_addr[0..6]); debug!("Interface {} has MAC address {:?}", name, hw_addr); if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { if if_info.hw_addr.is_none() { if_info.hw_addr = Some(hw_addr); } } else { if_infos.push(InterfaceInfo { name: String::from(name), other_name: None, index, ip_addr: Ipv4Addr::BROADCAST, hw_addr: Some(hw_addr), }); } } } } #[cfg(not(any(target_os = "linux", target_os = "hurd")))] { use std::slice; if sa_family == AF_LINK as _ { // SAFETY: If the address family is AF_LINK then it is actually a valid sockaddr_dl // pointer and can be used as such. let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_dl) }; if addr.sdl_nlen <= IF_NAMESIZE as u8 && addr.sdl_alen == 6 { let mut hw_addr = [0u8; 6]; // SAFETY: There can be more than the given number of bytes stored and // this happens regularly on macOS at least. It is required that the // interface name is at most IF_NAMESIZE (checked just above). unsafe { let sdl_addr_ptr = addr.sdl_data.as_ptr() as *const u8; let sdl_addr = slice::from_raw_parts(sdl_addr_ptr.add(addr.sdl_nlen as usize), 6); hw_addr.copy_from_slice(sdl_addr); } debug!("Interface {} has MAC address {:?}", name, hw_addr); if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { if if_info.hw_addr.is_none() { if_info.hw_addr = Some(hw_addr); } } else { if_infos.push(InterfaceInfo { name: String::from(name), other_name: None, index, ip_addr: Ipv4Addr::BROADCAST, hw_addr: Some(hw_addr), }); } } } } } if_infos.retain(|iface| !iface.ip_addr.is_broadcast()); Ok(if_infos) } /// Create an `UdpSocket` and bind it to the given address but set `SO_REUSEADDR` and/or /// `SO_REUSEPORT` before doing so. /// /// `UdpSocket::bind()` does not allow setting custom options before binding. pub fn create_udp_socket( addr: &Ipv4Addr, port: u16, iface: Option<&InterfaceInfo>, ) -> Result { use std::os::unix::io::FromRawFd; /// Helper struct to keep a raw fd and close it on drop struct Fd(i32); impl Drop for Fd { fn drop(&mut self) { unsafe { // SAFETY: The integer is a valid fd by construction. let _ = close(self.0); } } } // SAFETY: Calling socket() is safe at any time and will simply fail if invalid parameters // are passed. let fd = unsafe { #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "solaris", target_os = "illumos", target_os = "hurd", ))] let ty = SOCK_DGRAM | SOCK_CLOEXEC; #[cfg(target_os = "macos")] let ty = SOCK_DGRAM; let res = socket(AF_INET, ty, 0); if res == -1 { return Err(io::Error::last_os_error()); } Fd(res) }; // SAFETY: A valid socket fd is passed to ioctl() and setsockopt() and the parameters to // setsockopt() are according to the type expected by SO_NOSIGPIPE. #[cfg(target_os = "macos")] unsafe { let res = ioctl(fd.0, FIOCLEX); if res == -1 { return Err(io::Error::last_os_error()); } let val = 1i32; let res = setsockopt( fd.0, SOL_SOCKET, SO_NOSIGPIPE, &val as *const _ as *const _, mem::size_of_val(&val) as _, ); if res < 0 { return Err(io::Error::last_os_error()); } } // SAFETY: A valid socket fd is passed here. unsafe { set_reuse(fd.0); if let Some(iface) = iface { bind_to_interface(fd.0, iface); } } // SAFETY: A valid socket fd is passed together with a valid sockaddr_in and its size. unsafe { let addr = sockaddr_in { sin_family: AF_INET as _, sin_port: u16::to_be(port), sin_addr: in_addr { s_addr: u32::from_ne_bytes(addr.octets()), }, sin_zero: [0u8; 8], #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "hurd", ))] sin_len: mem::size_of::() as _, }; let res = bind( fd.0, &addr as *const _ as *const _, mem::size_of_val(&addr) as _, ); if res < 0 { return Err(io::Error::last_os_error()); } } unsafe { Ok(UdpSocket::from_raw_fd(mem::ManuallyDrop::new(fd).0)) } } /// Join multicast address for a given interface. pub fn join_multicast_v4( socket: &UdpSocket, addr: &Ipv4Addr, iface: &InterfaceInfo, ) -> Result<(), Error> { #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "macos")))] { let mreqn = ip_mreqn { imr_multiaddr: in_addr { s_addr: u32::from_ne_bytes(addr.octets()), }, imr_address: in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: iface.index as _, }; // SAFETY: Requires a valid ip_mreq or ip_mreqn struct to be passed together // with its size for checking which of the two it is. On errors a negative // integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn as *const _ as *const _, mem::size_of_val(&mreqn) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } #[cfg(not(any(target_os = "openbsd", target_os = "dragonfly", target_os = "netbsd")))] { let mreqn = ip_mreqn { imr_multiaddr: in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_address: in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: iface.index as _, }; // SAFETY: Requires a valid ip_mreq or ip_mreqn struct to be passed together // with its size for checking which of the two it is. On errors a negative // integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_MULTICAST_IF, &mreqn as *const _ as *const _, mem::size_of_val(&mreqn) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } } #[cfg(any(target_os = "openbsd", target_os = "dragonfly"))] { let addr = in_addr { s_addr: u32::from_ne_bytes(iface.ip_addr.octets()), }; // SAFETY: Requires a valid in_addr struct to be passed together with its size for // checking which of the two it is. On errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_MULTICAST_IF, &addr as *const _ as *const _, mem::size_of_val(&addr) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } } #[cfg(target_os = "netbsd")] { let idx = (iface.index as u32).to_be(); // SAFETY: Requires a valid in_addr struct or interface index in network byte order // to be passed together with its size for checking which of the two it is. On // errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_MULTICAST_IF, &idx as *const _ as *const _, mem::size_of_val(&idx) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } } Ok(()) } #[cfg(any(target_os = "solaris", target_os = "illumos"))] { socket .join_multicast_v4(addr, &iface.ip_addr) .with_context(|| { format!( "Failed joining multicast group for interface {} at address {}", iface.name, iface.ip_addr ) })?; let addr = in_addr { s_addr: u32::from_ne_bytes(iface.ip_addr.octets()), }; // SAFETY: Requires a valid in_addr struct to be passed together with its size for // checking which of the two it is. On errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_MULTICAST_IF, &addr as *const _ as *const _, mem::size_of_val(&addr) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed setting multicast interface {}", iface.name, ); } } Ok(()) } #[cfg(target_os = "macos")] { let mreq = ip_mreq { imr_multiaddr: in_addr { s_addr: u32::from_ne_bytes(addr.octets()), }, imr_address: in_addr { s_addr: u32::from_ne_bytes(iface.ip_addr.octets()), }, }; let mreqn = ip_mreqn { imr_multiaddr: in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_address: in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: iface.index as _, }; // SAFETY: Requires a valid ip_mreq struct to be passed together with its size for checking // validity. On errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq as *const _ as *const _, mem::size_of_val(&mreq) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } // SAFETY: Requires a valid ip_mreqn struct to be passed together // with its size for checking which of the two it is. On errors a negative // integer is returned. unsafe { if setsockopt( socket.as_raw_fd(), IPPROTO_IP, IP_MULTICAST_IF, &mreqn as *const _ as *const _, mem::size_of_val(&mreqn) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } Ok(()) } } /// Allow multiple sockets to bind to the same address / port. /// /// This is best-effort and might not actually do anything. /// /// SAFETY: Must be called with a valid socket fd. unsafe fn set_reuse(socket: i32) { // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and // enables the given feature on the socket. // // We explicitly ignore errors here. If it works, good, if it doesn't then not much // lost other than the ability to run multiple processes at once. unsafe { let v = 1i32; let res = setsockopt( socket, SOL_SOCKET, SO_REUSEADDR, &v as *const _ as *const _, mem::size_of_val(&v) as u32, ); if res < 0 { warn!("Failed to set SO_REUSEADDR on socket"); } } #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] { // SAFETY: SO_REUSEPORT takes an i32 value that can be 0/false or 1/true and // enables the given feature on the socket. // // We explicitly ignore errors here. If it works, good, if it doesn't then not much // lost other than the ability to run multiple processes at once. unsafe { let v = 1i32; let res = setsockopt( socket, SOL_SOCKET, SO_REUSEPORT, &v as *const _ as *const _, mem::size_of_val(&v) as u32, ); if res < 0 { warn!("Failed to set SO_REUSEPORT on socket"); } } } } /// Bind the socket to a specific interface. /// /// This is best-effort and might not actually do anything. /// /// SAFETY: Must be called with a valid socket fd. #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] unsafe fn bind_to_interface(socket: i32, iface: &InterfaceInfo) { // On Linux, go one step further and bind the socket completely to the socket if we // can, i.e. have the relevant permissions. #[cfg(target_os = "linux")] { // SAFETY: The socket passed in must be valid and the SO_BINDTOIFINDEX socket option // takes an `i32` that represents the interface index as parameter. let res = unsafe { let v = iface.index as i32; setsockopt( socket, SOL_SOCKET, SO_BINDTOIFINDEX, &v as *const _ as *const _, mem::size_of_val(&v) as u32, ) }; if res < 0 { warn!("Failed to set SO_BINDTOIFINDEX on socket, trying SO_BINDTODEVICE"); if iface.name.len() >= 16 { warn!( "Interface name '{}' too long for SO_BINDTODEVICE", iface.name ); } else { // SAFETY: The socket passed in must be valid and the SO_BINDTODEVICE socket option // takes a NUL-terminated byte array of up to 16 bytes as parameter. unsafe { let mut v = [0u8; 16]; v[..iface.name.len()].copy_from_slice(iface.name.as_bytes()); let res = setsockopt( socket, SOL_SOCKET, SO_BINDTODEVICE, &v as *const _ as *const _, (iface.name.len() + 1) as u32, ); if res < 0 { warn!("Failed to set SO_BINDTODEVICE on socket"); } } } } } } } #[cfg(windows)] mod imp { use super::*; use std::{ ffi::{CStr, OsString}, io, marker, mem, net::{Ipv4Addr, SocketAddr, UdpSocket}, os::{ raw::*, windows::{ffi::OsStringExt, io::AsRawSocket, raw::SOCKET}, }, ptr, slice, }; use crate::{error::Context, ffi::windows::*}; /// Returns information for all non-loopback, multicast-capable network interfaces. pub fn query_interfaces() -> Result, Error> { struct AdapterAddresses { addresses: ptr::NonNull, heap: isize, } impl AdapterAddresses { fn new() -> io::Result { // SAFETY: Gets the process's default heap and is safe to be called at any time. let heap = unsafe { GetProcessHeap() }; // SAFETY: GetAdaptersAddresses() requires allocated memory to be passed in. // In the beginning 16kB are allocated via HeapAlloc() from the default process's // heap (see above), then passed to GetAdaptersAddresses(). // // If this returns ERROR_NOT_ENOUGH_MEMORY then this was not enough memory and the // required amount is returned as out parameter. In that case we loop up to 10 // times, reallocate memory via HeapReAlloc() and try again. // // On other errors the memory is freed before returning via HeapFree(), or when 10 // iterations were reached. unsafe { let mut alloc_len = 16_384; let mut tries = 0; let mut addresses: *mut IP_ADAPTER_ADDRESSES_LH = ptr::null_mut(); loop { if tries > 10 { HeapFree(heap, 0, addresses as *mut _); return Err(io::Error::from(io::ErrorKind::OutOfMemory)); } if addresses.is_null() { addresses = HeapAlloc(heap, 0, alloc_len as usize) as *mut _; } else { addresses = HeapReAlloc(heap, 0, addresses as *mut _, alloc_len as usize) as *mut _; } if addresses.is_null() { return Err(io::Error::from(io::ErrorKind::OutOfMemory)); } let res = GetAdaptersAddresses( AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, ptr::null_mut(), addresses, &mut alloc_len, ); if res == 0 { return Ok(AdapterAddresses { heap, addresses: ptr::NonNull::new_unchecked(addresses), }); } else if res == ERROR_NOT_ENOUGH_MEMORY { tries += 1; continue; } else { HeapFree(heap, 0, addresses as *mut _); return Err(io::Error::from_raw_os_error(res as i32)); } } } } fn iter(&self) -> AdapterAddressesIter { AdapterAddressesIter { ptr: Some(self.addresses), phantom: marker::PhantomData, } } } impl Drop for AdapterAddresses { fn drop(&mut self) { // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by construction // and dropped only once, so freeing it here is OK. It might be NULL so check for // that first. // // Heap is the process's heap as set in the constructor above. unsafe { HeapFree(self.heap, 0, self.addresses.as_ptr() as *mut _); } } } struct AdapterAddressesIter<'a> { ptr: Option>, phantom: marker::PhantomData<&'a AdapterAddresses>, } impl<'a> Iterator for AdapterAddressesIter<'a> { type Item = &'a IP_ADAPTER_ADDRESSES_LH; fn next(&mut self) -> Option { match self.ptr { None => None, Some(ptr) => { // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by // construction so creating a reference to it is OK. let addr = unsafe { &*ptr.as_ptr() }; self.ptr = ptr::NonNull::new(addr.next); Some(addr) } } } } struct UnicastAddressesIter<'a> { ptr: Option>, phantom: marker::PhantomData<&'a IP_ADAPTER_ADDRESSES_LH>, } impl<'a> UnicastAddressesIter<'a> { fn new(addresses: &'a IP_ADAPTER_ADDRESSES_LH) -> Self { Self { ptr: ptr::NonNull::new(addresses.firstunicastaddress), phantom: marker::PhantomData, } } } impl<'a> Iterator for UnicastAddressesIter<'a> { type Item = &'a IP_ADAPTER_UNICAST_ADDRESS_LH; fn next(&mut self) -> Option { match self.ptr { None => None, Some(ptr) => { let addr = unsafe { &*ptr.as_ptr() }; self.ptr = ptr::NonNull::new(addr.next); Some(addr) } } } } let addresses = AdapterAddresses::new().context("Failed getting adapter addresses")?; let mut if_infos = Vec::::new(); for address in addresses.iter() { // SAFETY: adaptername points to a NUL-terminated ASCII name string that is valid // as long as its struct is let adaptername = unsafe { CStr::from_ptr(address.adaptername as *const c_char) } .to_str() .unwrap(); // Skip adapters that are receive-only, can't do multicast or don't have IPv4 support // as they're not usable in a PTP context. if address.flags & ADAPTER_FLAG_RECEIVE_ONLY != 0 { debug!("Interface {} is receive-only interface", adaptername); continue; } if address.flags & ADAPTER_FLAG_NO_MULTICAST != 0 { debug!("Interface {} does not support multicast", adaptername); continue; } if address.flags & ADAPTER_FLAG_IPV4_ENABLED == 0 { debug!("Interface {} has no IPv4 address", adaptername); continue; } // Skip adapters that are loopback or not up. if address.iftype == IF_TYPE_SOFTWARE_LOOPBACK { debug!("Interface {} is loopback interface", adaptername); continue; } if address.operstatus != IF_OPER_STATUS_UP { debug!("Interface {} is not up", adaptername); continue; } // SAFETY: Both fields of the union are always valid let index = unsafe { address.anonymous.anonymous.ifindex } as usize; // Skip adapters that have no valid interface index as they can't be used to join the // PTP multicast group reliably for this interface only. if index == 0 { debug!("Interface {} has no valid interface index", adaptername); continue; } // SAFETY: friendlyname is a NUL-terminated UCS2/wide string or NULL. let friendlyname = unsafe { if !address.friendlyname.is_null() { let len = { let mut len = 0; while *address.friendlyname.add(len) != 0 { len += 1; } len }; let f = slice::from_raw_parts(address.friendlyname, len); let f = OsString::from_wide(f); Some(String::from(f.to_str().unwrap())) } else { None } }; let mut hw_addr = None; if address.physicaladdresslength == 6 { let mut addr = [0u8; 6]; addr.copy_from_slice(&address.physicaladdress[..6]); hw_addr = Some(addr); } let ip_addr = UnicastAddressesIter::new(address).find_map(|addr| { if addr.address.lpsocketaddr.is_null() { return None; } // SAFETY: lpsocketaddr is a valid, non-NULL socket address and its family // field can be read to distinguish IPv4 and other socket addresses if unsafe { (*addr.address.lpsocketaddr).sa_family } != AF_INET as u16 { return None; } Some(Ipv4Addr::from( // SAFETY: lpsocketaddr is a valid, non-NULL IPv4 socket address as checked // above and can be dereferenced as such. unsafe { (*addr.address.lpsocketaddr) .in_addr .S_un .S_addr .to_ne_bytes() }, )) }); if let Some(ip_addr) = ip_addr { if_infos.push(InterfaceInfo { name: String::from(adaptername), other_name: friendlyname, index, ip_addr, hw_addr, }); } else { debug!("Interface {} has no IPv4 address", adaptername); } } Ok(if_infos) } /// Create an `UdpSocket` and bind it to the given address but set `SO_REUSEADDR` and/or /// `SO_REUSEPORT` before doing so. /// /// `UdpSocket::bind()` does not allow setting custom options before binding. pub fn create_udp_socket( addr: &Ipv4Addr, port: u16, _iface: Option<&InterfaceInfo>, ) -> Result { use std::os::windows::io::FromRawSocket; // XXX: Make sure Rust std is calling WSAStartup() let _ = UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)))?; /// Helper struct to keep a raw socket and close it on drop struct Socket(SOCKET); impl Drop for Socket { fn drop(&mut self) { unsafe { // SAFETY: The socket is valid by construction. let _ = closesocket(self.0); } } } // SAFETY: Calling WSASocketW() is safe at any time and will simply fail if invalid parameters // are passed or something else goes wrong. let socket = unsafe { let res = WSASocketW( AF_INET as _, SOCK_DGRAM as _, 0, ptr::null_mut(), 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT, ); if res == INVALID_SOCKET { return Err(io::Error::from_raw_os_error(WSAGetLastError())); } Socket(res) }; // SAFETY: A valid socket is passed here. unsafe { set_reuse(socket.0); } // SAFETY: A valid socket fd is passed together with a valid SOCKADDR and its size. unsafe { let addr = SOCKADDR { sa_family: AF_INET as _, sin_port: u16::to_be(port), in_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_ne_bytes(addr.octets()), }, }, sin_zero: [0; 8], }; let res = bind(socket.0, &addr, mem::size_of_val(&addr) as _); if res < 0 { return Err(io::Error::from_raw_os_error(WSAGetLastError())); } } unsafe { Ok(UdpSocket::from_raw_socket(mem::ManuallyDrop::new(socket).0)) } } // Join multicast address for a given interface. pub fn join_multicast_v4( socket: &UdpSocket, addr: &Ipv4Addr, iface: &InterfaceInfo, ) -> Result<(), Error> { let mreq = IP_MREQ { imr_multiaddr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_ne_bytes(addr.octets()), }, }, imr_address: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_ne_bytes(Ipv4Addr::new(0, 0, 0, iface.index as u8).octets()), }, }, }; // SAFETY: Requires a valid ip_mreq struct to be passed together with its size for checking // validity. On errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_socket(), IPPROTO_IP as i32, IP_ADD_MEMBERSHIP as i32, &mreq as *const _ as *const _, mem::size_of_val(&mreq) as _, ) < 0 { bail!( source: io::Error::from_raw_os_error(WSAGetLastError()), "Failed joining multicast group for interface {}", iface.name, ); } } let addr = IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_ne_bytes(Ipv4Addr::new(0, 0, 0, iface.index as u8).octets()), }, }; // SAFETY: Requires a valid IN_ADDR struct to be passed together with its size for checking // which of the two it is. On errors a negative integer is returned. unsafe { if setsockopt( socket.as_raw_socket(), IPPROTO_IP as i32, IP_MULTICAST_IF as i32, &addr as *const _ as *const _, mem::size_of_val(&addr) as _, ) < 0 { bail!( source: io::Error::last_os_error(), "Failed joining multicast group for interface {}", iface.name, ); } } Ok(()) } /// Allow multiple sockets to bind to the same address / port. /// /// This is best-effort and might not actually do anything. /// /// SAFETY: Must be called with a valid socket. unsafe fn set_reuse(socket: SOCKET) { // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and // enables the given feature on the socket. // // We explicitly ignore errors here. If it works, good, if it doesn't then not much // lost other than the ability to run multiple processes at once. unsafe { let v = 1i32; let res = setsockopt( socket, SOL_SOCKET as i32, SO_REUSEADDR as i32, &v as *const _ as *const _, mem::size_of_val(&v) as _, ); if res < 0 { warn!("Failed to set SO_REUSEADDR on socket"); } } } } pub use imp::*; #[cfg(test)] mod test { #[test] fn test_query_interfaces() { let ifaces = super::query_interfaces().unwrap(); for iface in ifaces { assert!(!iface.name.is_empty()); assert_ne!(iface.index, 0); assert!(!iface.ip_addr.is_unspecified()); } } #[test] fn test_create_socket_join_multicast() { let ifaces = super::query_interfaces().unwrap(); let iface = if ifaces.is_empty() { return; } else { &ifaces[0] }; let socket = super::create_udp_socket(&std::net::Ipv4Addr::UNSPECIFIED, 0, None).unwrap(); super::join_multicast_v4(&socket, &std::net::Ipv4Addr::new(224, 0, 0, 1), iface).unwrap(); let local_addr = socket.local_addr().unwrap(); let socket2 = std::net::UdpSocket::bind(std::net::SocketAddr::from(( std::net::Ipv4Addr::UNSPECIFIED, 0, ))) .unwrap(); socket2 .send_to( &[1, 2, 3, 4], std::net::SocketAddr::from((std::net::Ipv4Addr::LOCALHOST, local_addr.port())), ) .unwrap(); let mut buf = [0u8; 4]; socket.recv(&mut buf).unwrap(); assert_eq!(buf, [1, 2, 3, 4]); } }