// 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 //! Helper process that runs setuid root or with appropriate privileges to //! listen on ports < 1024, do multicast operations and get MAC addresses of //! interfaces. Privileges are dropped after these operations are done. //! //! It listens on the PTP multicast group on port 319 and 320 and forwards //! everything received there to stdout, while forwarding everything received //! on stdin to those sockets. //! Additionally it provides the MAC address of a network interface via stdout use std::{ io::{Read, Write}, net::{Ipv4Addr, SocketAddr, UdpSocket}, }; mod args; mod error; mod ffi; mod io; mod net; mod privileges; mod rand; mod thread; use error::{Context, Error}; use rand::rand; /// PTP Multicast group. const PTP_MULTICAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 1, 129); /// PTP Event message port. const PTP_EVENT_PORT: u16 = 319; /// PTP General message port. const PTP_GENERAL_PORT: u16 = 320; /// StdIO Message Types. /// PTP message for the event socket. const MSG_TYPE_EVENT: u8 = 0; /// PTP message for the general socket. const MSG_TYPE_GENERAL: u8 = 1; /// Clock ID message const MSG_TYPE_CLOCK_ID: u8 = 2; /// Create a new `UdpSocket` for the given port and configure it for PTP. fn create_socket(port: u16) -> Result { let socket = UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, port))) .with_context(|| format!("Failed to bind socket to port {}", port))?; socket.set_ttl(1).context("Failed setting TTL on socket")?; socket .set_multicast_ttl_v4(1) .context("Failed to set multicast TTL on socket")?; net::set_reuse(&socket); Ok(socket) } /// Join the multicast groups for PTP on the configured interfaces. fn join_multicast( args: &args::Args, event_socket: &UdpSocket, general_socket: &UdpSocket, ) -> Result<[u8; 8], Error> { let mut ifaces = net::query_interfaces().context("Failed to query network interfaces")?; if ifaces.is_empty() { bail!("No suitable network interfaces for PTP found"); } if !args.interfaces.is_empty() { ifaces.retain(|iface| { for filter_iface in &args.interfaces { if &iface.name == filter_iface { return true; } if let Some(ref other_name) = iface.other_name { if other_name == filter_iface { return true; } } if let Ok(addr) = filter_iface.parse::() { if addr == iface.ip_addr { return true; } } } false }); if ifaces.is_empty() { bail!("None of the selected network interfaces found"); } if ifaces.len() != args.interfaces.len() { bail!("Not all selected network interfaces found"); } } for socket in [&event_socket, &general_socket].iter() { for iface in &ifaces { net::join_multicast_v4(socket, &PTP_MULTICAST_ADDR, iface) .context("Failed to join multicast group")?; } } let clock_id = if args.clock_id == 0 { ifaces .iter() .find_map(|iface| iface.hw_addr) .map(|hw_addr| { [ hw_addr[0], hw_addr[1], hw_addr[2], 0xff, 0xfe, hw_addr[3], hw_addr[4], hw_addr[5], ] }) .unwrap_or_else(rand) } else { args.clock_id.to_be_bytes() }; Ok(clock_id) } fn main() -> Result<(), Error> { let args = args::parse_args().context("Failed parsing commandline parameters")?; let event_socket = create_socket(PTP_EVENT_PORT).context("Failed creating event socket")?; let general_socket = create_socket(PTP_GENERAL_PORT).context("Failed creating general socket")?; thread::set_priority().context("Failed to set thread priority")?; privileges::drop().context("Failed dropping privileges")?; let clock_id = join_multicast(&args, &event_socket, &general_socket) .context("Failed joining multicast groups")?; let mut poll = io::Poll::new(event_socket, general_socket).context("Failed creating poller")?; // Write clock ID first { let mut clock_id_data = [0u8; 3 + 8]; clock_id_data[0..2].copy_from_slice(&8u16.to_be_bytes()); clock_id_data[2] = MSG_TYPE_CLOCK_ID; clock_id_data[3..].copy_from_slice(&clock_id); poll.stdout() .write_all(&clock_id_data) .context("Failed writing to stdout")?; } // Now read-write from stdin/stdout and the sockets // // We assume that stdout never blocks and stdin receives a complete valid packet whenever it is // ready and never blocks in the middle of a packet. let mut socket_buffer = [0u8; 8192]; let mut stdinout_buffer = [0u8; 8192 + 3]; loop { let poll_res = poll.poll().context("Failed polling")?; // If any of the sockets are ready, continue reading packets from them until no more // packets are left and directly forward them to stdout. 'next_socket: for idx in [poll_res.event_socket, poll_res.general_socket] .iter() .enumerate() .filter_map(|(idx, r)| if *r { Some(idx) } else { None }) { let idx = idx as u8; let res = match idx { MSG_TYPE_EVENT => poll.event_socket().recv(&mut socket_buffer), MSG_TYPE_GENERAL => poll.general_socket().recv(&mut socket_buffer), _ => unreachable!(), }; match res { Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { continue 'next_socket; } Err(err) => { bail!( source: err, "Failed reading from {} socket", if idx == 0 { "event" } else { "general" } ); } Ok(read) => { stdinout_buffer[0..2].copy_from_slice(&(read as u16).to_be_bytes()); stdinout_buffer[2] = idx; stdinout_buffer[3..][..read].copy_from_slice(&socket_buffer[..read]); poll.stdout() .write_all(&stdinout_buffer[..(read + 3)]) .context("Failed writing to stdout")?; } } } // After handling the sockets check if a packet is available on stdin, read it and forward // it to the corresponding socket. if poll_res.stdin { poll.stdin() .read_exact(&mut stdinout_buffer[0..3]) .context("Failed reading packet header from stdin")?; let size = u16::from_be_bytes([stdinout_buffer[0], stdinout_buffer[1]]); if size as usize > stdinout_buffer.len() { bail!("Invalid packet size on stdin {}", size); } let type_ = stdinout_buffer[2]; poll.stdin() .read_exact(&mut stdinout_buffer[0..size as usize]) .context("Failed reading packet body from stdin")?; let buf = &stdinout_buffer[0..size as usize]; match type_ { MSG_TYPE_EVENT => poll .event_socket() .send_to(buf, (PTP_MULTICAST_ADDR, PTP_EVENT_PORT)), MSG_TYPE_GENERAL => poll .general_socket() .send_to(buf, (PTP_MULTICAST_ADDR, PTP_GENERAL_PORT)), _ => unreachable!(), } .with_context(|| { format!( "Failed sending to {} socket", if type_ == 0 { "event" } else { "general" } ) })?; } } }