diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs index c452a1d7d2..a8c46b43d0 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs @@ -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; diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs index 7784e47a41..ece3329589 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs @@ -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 { - let socket = net::create_udp_socket(&Ipv4Addr::UNSPECIFIED, port) +fn create_socket(port: u16, iface: &net::InterfaceInfo) -> Result { + 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) diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs index 8f04988405..f8ad2fab60 100644 --- a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs @@ -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 { + 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 @@ -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 { + 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() @@ -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();