Support building TrustTunnel on FreeBSD OS

A bunch of code fixes and adjustments to make TrustTunnel build cleanly
on FreeBSD.

Note that because of the `boringssl` version currently used by `quiche`,
to build TrustTunnel on FreeBSD, one needs to set the
BORING_BSSL_RUST_CPPLIB=c++ variable. This is to make the linker use
libc++ from clang instead of libstdc++. A newer version of `boringssl`
has a fix that makes this variable obsolete.

Signed-off-by: Vladimir Krivopalov <vladimir.krivopalov@gmail.com>
This commit is contained in:
Vladimir Krivopalov 2026-01-25 12:45:44 +00:00
parent dea1ca4376
commit 3fb64c910e
3 changed files with 100 additions and 3 deletions

View file

@ -397,7 +397,7 @@ impl RawPacketStream {
};
unsafe {
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
let fd = libc::socket(family, libc::SOCK_RAW, protocol);
#[cfg(target_os = "macos")]
let fd = libc::socket(family, libc::SOCK_DGRAM, protocol);

View file

@ -1,10 +1,11 @@
#include <unistd.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef __linux__
#include <linux/icmp.h>
#endif
#include <netinet/icmp6.h>
#include <sys/socket.h>
/**
@ -49,7 +50,7 @@ int set_icmpv6_filter(int fd) {
}
int bind_to_interface_by_index(int fd, int family, unsigned idx) {
#ifndef __linux__
#ifdef __APPLE__
int level = IPPROTO_IP;
int option = IP_BOUND_IF;
if (family == AF_INET6) {
@ -59,6 +60,7 @@ int bind_to_interface_by_index(int fd, int family, unsigned idx) {
return setsockopt(fd, level, option, &idx, sizeof(idx));
#else
/* Linux and FreeBSD don't have IP_BOUND_IF */
(void)fd;
(void)family;
(void)idx;

View file

@ -166,6 +166,101 @@ pub(crate) fn bind_to_interface(
}
}
#[cfg(target_os = "freebsd")]
pub(crate) fn bind_to_interface(
fd: libc::c_int,
family: libc::c_int,
name: &str,
) -> io::Result<()> {
use std::ffi::CString;
// FreeBSD doesn't have SO_BINDTODEVICE (Linux) or IP_BOUND_IF (macOS).
// We bind the socket to the interface's IP address instead, which achieves
// the same effect of restricting traffic to that interface.
let c_name = CString::new(name).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "invalid interface name")
})?;
let mut ifaddrs: *mut libc::ifaddrs = std::ptr::null_mut();
// SAFETY: getifaddrs writes to our pointer, which we'll free with freeifaddrs
if unsafe { libc::getifaddrs(&mut ifaddrs) } != 0 {
return Err(io::Error::last_os_error());
}
// RAII guard to ensure freeifaddrs is called
struct IfAddrsGuard(*mut libc::ifaddrs);
impl Drop for IfAddrsGuard {
fn drop(&mut self) {
// SAFETY: self.0 was allocated by getifaddrs
unsafe { libc::freeifaddrs(self.0) };
}
}
let _guard = IfAddrsGuard(ifaddrs);
let mut current = ifaddrs;
let mut bind_addr: Option<libc::sockaddr_storage> = None;
while !current.is_null() {
// SAFETY: current is not null (checked above) and points to valid ifaddrs from getifaddrs
let ifa = unsafe { &*current };
let names_match = !ifa.ifa_name.is_null() && {
// SAFETY: ifa_name is not null (checked above) and is a valid C string from getifaddrs
unsafe { libc::strcmp(ifa.ifa_name, c_name.as_ptr()) == 0 }
};
if names_match && !ifa.ifa_addr.is_null() {
// SAFETY: ifa_addr is not null (checked above)
let sa_family = unsafe { (*ifa.ifa_addr).sa_family } as libc::c_int;
if sa_family == family {
// SAFETY: zeroed sockaddr_storage is valid
let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let len = if family == libc::AF_INET {
std::mem::size_of::<libc::sockaddr_in>()
} else {
std::mem::size_of::<libc::sockaddr_in6>()
};
// SAFETY: ifa_addr points to valid sockaddr of the appropriate family,
// and storage has enough space for either sockaddr_in or sockaddr_in6
unsafe {
std::ptr::copy_nonoverlapping(
ifa.ifa_addr as *const u8,
&mut storage as *mut _ as *mut u8,
len,
);
}
bind_addr = Some(storage);
break;
}
}
current = ifa.ifa_next;
}
let storage = bind_addr.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"no {} address found for interface {}",
if family == libc::AF_INET { "IPv4" } else { "IPv6" },
name
),
)
})?;
let addr_len = if family == libc::AF_INET {
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t
} else {
std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t
};
// SAFETY: storage contains a valid sockaddr_in or sockaddr_in6, fd is a valid socket
if unsafe { libc::bind(fd, &storage as *const _ as *const libc::sockaddr, addr_len) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn set_socket_ttl(fd: libc::c_int, is_ipv4: bool, ttl: u8) -> io::Result<()> {
unsafe {
let (level, name) = if is_ipv4 {