ptp: Use SO_BINDTOIFINDEX / SO_BINDTODEVICE on Linux

This makes sure we really really really only get packets from the
desired interface as passing a device to IP_ADD_MEMBERSHIP apparently
does not have this effect alone.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5584>
This commit is contained in:
Sebastian Dröge 2023-11-01 16:24:21 +02:00 committed by GStreamer Marge Bot
parent f6ffe34ad5
commit ffa30637c4
3 changed files with 83 additions and 7 deletions

View file

@ -99,6 +99,12 @@ pub mod unix {
))]
pub const SOL_SOCKET: c_int = 1;
#[cfg(target_os = "linux")]
pub const SO_BINDTODEVICE: c_int = 25;
#[cfg(target_os = "linux")]
pub const SO_BINDTOIFINDEX: c_int = 62;
#[cfg(target_os = "macos")]
pub const FIOCLEX: c_ulong = 0x20006601;

View file

@ -58,8 +58,8 @@ const MSG_TYPE_CLOCK_ID: u8 = 2;
const MSG_TYPE_SEND_TIME_ACK: u8 = 3;
/// Create a new `UdpSocket` for the given port and configure it for PTP.
fn create_socket(port: u16) -> Result<UdpSocket, Error> {
let socket = net::create_udp_socket(&Ipv4Addr::UNSPECIFIED, port)
fn create_socket(port: u16, iface: &net::InterfaceInfo) -> Result<UdpSocket, Error> {
let socket = net::create_udp_socket(&Ipv4Addr::UNSPECIFIED, port, Some(iface))
.with_context(|| format!("Failed to bind socket to port {}", port))?;
socket
@ -123,9 +123,10 @@ fn run() -> Result<(), Error> {
for iface in &ifaces {
info!("Binding to interface {}", iface.name);
let event_socket = create_socket(PTP_EVENT_PORT).context("Failed creating event socket")?;
let event_socket =
create_socket(PTP_EVENT_PORT, iface).context("Failed creating event socket")?;
let general_socket =
create_socket(PTP_GENERAL_PORT).context("Failed creating general socket")?;
create_socket(PTP_GENERAL_PORT, iface).context("Failed creating general socket")?;
for socket in [&event_socket, &general_socket].iter() {
net::join_multicast_v4(socket, &PTP_MULTICAST_ADDR, iface)

View file

@ -266,7 +266,11 @@ mod imp {
/// `SO_REUSEPORT` before doing so.
///
/// `UdpSocket::bind()` does not allow setting custom options before binding.
pub fn create_udp_socket(addr: &Ipv4Addr, port: u16) -> Result<UdpSocket, io::Error> {
pub fn create_udp_socket(
addr: &Ipv4Addr,
port: u16,
iface: Option<&InterfaceInfo>,
) -> Result<UdpSocket, io::Error> {
use std::os::unix::io::FromRawFd;
/// Helper struct to keep a raw fd and close it on drop
@ -328,6 +332,9 @@ mod imp {
// 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.
@ -488,6 +495,7 @@ mod imp {
Ok(())
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
{
use crate::error::Context;
@ -576,6 +584,63 @@ mod imp {
}
}
}
/// 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)]
@ -850,7 +915,11 @@ mod imp {
/// `SO_REUSEPORT` before doing so.
///
/// `UdpSocket::bind()` does not allow setting custom options before binding.
pub fn create_udp_socket(addr: &Ipv4Addr, port: u16) -> Result<UdpSocket, io::Error> {
pub fn create_udp_socket(
addr: &Ipv4Addr,
port: u16,
_iface: Option<&InterfaceInfo>,
) -> Result<UdpSocket, io::Error> {
use std::os::windows::io::FromRawSocket;
// XXX: Make sure Rust std is calling WSAStartup()
@ -1026,7 +1095,7 @@ mod test {
&ifaces[0]
};
let socket = super::create_udp_socket(&std::net::Ipv4Addr::UNSPECIFIED, 0).unwrap();
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();