Skip to content
Snippets Groups Projects
Commit 2515d902 authored by Sebastian Dröge's avatar Sebastian Dröge :tea:
Browse files

ptp-helper: Rewrite in Rust for portability and security

parent fe580cac
No related branches found
No related tags found
No related merge requests found
......@@ -289,7 +289,7 @@ def get_subprocess_env(options, gst_version):
env["GST_PLUGIN_SCANNER"] = os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir)
env["GST_PTP_HELPER"] = os.path.normpath(
"%s/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper" % options.builddir)
"%s/subprojects/gstreamer/libs/gst/helpers/ptp/gst-ptp-helper" % options.builddir)
if os.name == 'nt':
lib_path_envvar = 'PATH'
......
This diff is collapsed.
subdir('ptp')
exe = executable('gst-plugin-scanner',
'gst-plugin-scanner.c',
c_args : gst_c_args,
......@@ -24,107 +26,6 @@ if bashcomp_found
)
endif
# Check PTP support
have_ptp = false
if host_system == 'android'
message('PTP not supported on Android because of permissions.')
elif host_system == 'windows'
message('PTP not supported on Windows, not ported yet.')
elif host_system == 'ios'
message('PTP not supported on iOS because of permissions.')
elif ['linux', 'darwin', 'netbsd', 'freebsd', 'openbsd', 'kfreebsd', 'dragonfly', 'sunos', 'gnu', 'gnu/kfreebsd'].contains(host_system)
message('PTP supported on ' + host_system + '.')
have_ptp = true
else
message('PTP not supported on ' + host_system + ', not ported yet.')
endif
if have_ptp
cdata.set('HAVE_PTP', 1, description : 'PTP support available')
if cc.compiles('''#include <sys/ioctl.h>
#include <net/if.h>
int some_func (void) {
struct ifreq ifr;
struct ifconf ifc;
ioctl(0, SIOCGIFCONF, &ifc);
ioctl(0, SIOCGIFFLAGS, &ifr);
ioctl(0, SIOCGIFHWADDR, &ifr);
return ifr.ifr_hwaddr.sa_data[0];
}''',
name : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR available')
cdata.set('HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR', 1,
description : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR is available')
endif
if cc.compiles('''#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
int some_func (void) {
struct ifaddrs *ifaddr;
getifaddrs(&ifaddr);
return (ifaddr->ifa_flags & IFF_LOOPBACK) && ifaddr->ifa_addr->sa_family != AF_LINK;
}''', name : 'getifaddrs() and AF_LINK available')
cdata.set('HAVE_GETIFADDRS_AF_LINK', 1,
description : 'getifaddrs() and AF_LINK is available')
endif
setcap_prog = find_program('setcap', '/usr/sbin/setcap', '/sbin/setcap', required : false)
cap_dep = dependency('libcap', required: false)
# user/group to change to in gst-ptp-helper
ptp_helper_setuid_user = get_option('ptp-helper-setuid-user')
if ptp_helper_setuid_user != ''
cdata.set_quoted('HAVE_PTP_HELPER_SETUID_USER', ptp_helper_setuid_user,
description : 'PTP helper setuid user')
endif
ptp_helper_setuid_group = get_option('ptp-helper-setuid-group')
if ptp_helper_setuid_group != ''
cdata.set_quoted('HAVE_PTP_HELPER_SETUID_GROUP', ptp_helper_setuid_group,
description : 'PTP helper setuid group')
endif
# how to install gst-ptp-helper
with_ptp_helper_permissions = get_option('ptp-helper-permissions')
if with_ptp_helper_permissions == 'auto'
if setcap_prog.found() and cap_dep.found()
with_ptp_helper_permissions = 'capabilities'
else
with_ptp_helper_permissions = 'setuid-root'
endif
endif
message('How to install gst-ptp-helper: ' + with_ptp_helper_permissions)
if with_ptp_helper_permissions == 'none'
# nothing to do
elif with_ptp_helper_permissions == 'setuid-root'
cdata.set('HAVE_PTP_HELPER_SETUID', 1,
description : 'Use setuid-root for permissions in PTP helper')
elif with_ptp_helper_permissions == 'capabilities'
if not setcap_prog.found()
error('capabilities-based ptp-helper-permissions requested, but could not find setcap tool.')
elif not cap_dep.found()
error('capabilities-based ptp-helper-permissions requested, but could not find libcap.')
endif
cdata.set('HAVE_PTP_HELPER_CAPABILITIES', 1,
description : 'Use capabilities for permissions in PTP helper')
else
error('Unexpected ptp helper permissions value: ' + with_ptp_helper_permissions)
endif
exe = executable('gst-ptp-helper', 'gst-ptp-helper.c',
c_args : gst_c_args,
include_directories : [configinc, libsinc],
dependencies : [gst_dep, gio_dep, mathlib, cap_dep],
install_dir : helpers_install_dir,
install : true)
meson.add_install_script('ptp_helper_post_install.sh',
helpers_install_dir, with_ptp_helper_permissions,
setcap_prog.found() ? setcap_prog.full_path() : '')
meson.add_devenv({'GST_PTP_HELPER': exe.full_path()})
endif
install_data(['gst_gdb.py', 'glib_gobject_helper.py'],
install_dir : join_paths(get_option('datadir'), 'gstreamer-@0@'.format(apiversion), 'gdb'),
install_tag : 'devel')
......
// GStreamer
//
// Copyright (C) 2015-2023 Sebastian Dröge <sebastian@centricular.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// 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 stdout to those sockets.
//! Additionally it provides the MAC address of a network interface via stdout
use std::{
borrow::Cow,
error::Error,
fmt::{Debug, Display},
io::{self, Read, Write},
net::{Ipv4Addr, SocketAddr, UdpSocket},
sync::{mpsc, Arc},
thread,
};
const PTP_MULTICAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 1, 129);
const PTP_EVENT_PORT: u16 = 319;
const PTP_GENERAL_PORT: u16 = 320;
/// Custom error type that just holds a string.
struct StrError(Cow<'static, str>);
impl Debug for StrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl Display for StrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl Error for StrError {}
impl From<&'static str> for StrError {
fn from(value: &'static str) -> Self {
StrError(value.into())
}
}
impl From<String> for StrError {
fn from(value: String) -> Self {
StrError(value.into())
}
}
/// Simple XOR-shift implementation.
#[derive(Debug)]
struct Xorshift64State(u64);
impl Default for Xorshift64State {
fn default() -> Self {
Self(0xdead_b33f_badc_00fe)
}
}
impl Xorshift64State {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}
/// Parsed command-line arguments.
#[derive(Debug)]
struct Args {
interfaces: Vec<String>,
#[allow(dead_code)]
verbose: bool,
clock_id: u64,
}
fn parse_args() -> Result<Args, StrError> {
use std::env;
let mut interfaces = Vec::new();
let mut verbose = false;
let mut clock_id = 0;
let mut args = env::args();
// Skip executable name
let _ = args.next();
while let Some(arg) = args.next() {
match arg.as_str() {
"-v" | "--verbose" => {
verbose = true;
}
"-i" | "--interface" => {
let iface = args.next().ok_or_else(|| "No interface following -i")?;
interfaces.push(iface);
}
"-c" | "--clock-id" => {
let clock_id_arg = args.next().ok_or_else(|| "No clock-id following -c")?;
clock_id = clock_id_arg
.parse::<u64>()
.map_err(|err| format!("Invalid clock ID {err}"))?;
}
arg => {
return Err(format!("Unknown command-line argument {arg}").into());
}
}
}
let args = Args {
interfaces,
verbose,
clock_id,
};
if verbose {
eprintln!("Running with arguments {args:#?}");
}
Ok(args)
}
// TODO: Proper error handling below
fn create_socket(args: &Args, port: u16) -> Result<Arc<UdpSocket>, StrError> {
let socket = UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, port)))
.map_err(|err| err.to_string())?;
if args.interfaces.is_empty() {
socket
.join_multicast_v4(&PTP_MULTICAST_ADDR, &Ipv4Addr::UNSPECIFIED)
.map_err(|err| err.to_string())?;
} else {
// TODO: Get interface addresses
socket
.join_multicast_v4(&PTP_MULTICAST_ADDR, &Ipv4Addr::UNSPECIFIED)
.map_err(|err| err.to_string())?;
}
socket.set_ttl(1).map_err(|err| err.to_string())?;
socket
.set_multicast_ttl_v4(1)
.map_err(|err| err.to_string())?;
socket
.set_multicast_loop_v4(true)
.map_err(|err| err.to_string())?;
Ok(Arc::new(socket))
}
fn receive_thread(
sender: &mut mpsc::SyncSender<Result<Vec<u8>, StrError>>,
socket: &UdpSocket,
type_: u8,
) -> Result<(), StrError> {
let mut buf = [0u8; 8192];
loop {
let read = socket.recv(&mut buf).map_err(|err| err.to_string())?;
let mut data = Vec::with_capacity(4 + read);
data.extend_from_slice(&(read as u16).to_ne_bytes());
data.push(type_);
data.push(0);
data.extend_from_slice(&buf[..read]);
sender.try_send(Ok(data)).map_err(|err| err.to_string())?;
}
}
fn stdin_thread(
_sender: &mut mpsc::SyncSender<Result<Vec<u8>, StrError>>,
socket_event: &UdpSocket,
socket_general: &UdpSocket,
) -> Result<(), StrError> {
let mut buf = [0u8; 8192];
let mut stdin = io::stdin().lock();
loop {
stdin
.read_exact(&mut buf[0..4])
.map_err(|err| err.to_string())?;
let size = u16::from_ne_bytes([buf[0], buf[1]]);
let type_ = buf[2];
stdin
.read_exact(&mut buf[0..size as usize])
.map_err(|err| err.to_string())?;
if type_ == 0 {
socket_event
.send_to(&buf[0..size as usize], (PTP_MULTICAST_ADDR, PTP_EVENT_PORT))
.map_err(|err| err.to_string())?;
} else if type_ == 1 {
socket_general
.send_to(
&buf[0..size as usize],
(PTP_MULTICAST_ADDR, PTP_GENERAL_PORT),
)
.map_err(|err| err.to_string())?;
}
}
}
fn main() -> Result<(), StrError> {
let args = parse_args()?;
let socket_event = create_socket(&args, PTP_EVENT_PORT)?;
let socket_general = create_socket(&args, PTP_GENERAL_PORT)?;
// TODO: Drop privileges
// TODO: Calculate a proper clock ID if none was given
let (mut sender, receiver) = mpsc::sync_channel::<Result<Vec<u8>, StrError>>(10);
thread::spawn({
let mut sender = sender.clone();
let socket_event = socket_event.clone();
move || {
let res = receive_thread(&mut sender, &socket_event, 0);
if let Err(err) = res {
let _ = sender.send(Err(err));
}
}
});
thread::spawn({
let mut sender = sender.clone();
let socket_general = socket_general.clone();
move || {
let res = receive_thread(&mut sender, &socket_general, 1);
if let Err(err) = res {
let _ = sender.send(Err(err));
}
}
});
thread::spawn({
move || {
let res = stdin_thread(&mut sender, &socket_event, &socket_general);
if let Err(err) = res {
let _ = sender.send(Err(err));
}
}
});
let mut stdout = io::stdout().lock();
// Write clock ID
let mut clock_id_data = Vec::with_capacity(4 + 8);
clock_id_data.extend_from_slice(&8u16.to_ne_bytes());
clock_id_data.push(2);
clock_id_data.push(0);
if args.clock_id == 0 {
// TODO: move to a function and get a better value here
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let pid = std::process::id();
let mut rng = Xorshift64State::new(now | ((pid as u64) << 32));
clock_id_data.extend_from_slice(&rng.next().to_be_bytes());
} else {
clock_id_data.extend_from_slice(&args.clock_id.to_be_bytes());
}
stdout
.write(&clock_id_data)
.map_err(|err| err.to_string())?;
stdout.flush().map_err(|err| err.to_string())?;
while let Ok(msg) = receiver.recv() {
let msg = msg?;
stdout.write(&msg).map_err(|err| err.to_string())?;
stdout.flush().map_err(|err| err.to_string())?;
}
Ok(())
}
# Check PTP support
have_rust = add_languages('rust', native : false, required : false)
have_ptp = false
if have_rust
have_ptp = true
else
message('PTP not supported without Rust compiler')
endif
if have_ptp
rustc = meson.get_compiler('rust')
cdata.set('HAVE_PTP', 1, description : 'PTP support available')
if cc.compiles('''#include <sys/ioctl.h>
#include <net/if.h>
int some_func (void) {
struct ifreq ifr;
struct ifconf ifc;
ioctl(0, SIOCGIFCONF, &ifc);
ioctl(0, SIOCGIFFLAGS, &ifr);
ioctl(0, SIOCGIFHWADDR, &ifr);
return ifr.ifr_hwaddr.sa_data[0];
}''',
name : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR available')
cdata.set('HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR', 1,
description : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR is available')
endif
if cc.compiles('''#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
int some_func (void) {
struct ifaddrs *ifaddr;
getifaddrs(&ifaddr);
return (ifaddr->ifa_flags & IFF_LOOPBACK) && ifaddr->ifa_addr->sa_family != AF_LINK;
}''', name : 'getifaddrs() and AF_LINK available')
cdata.set('HAVE_GETIFADDRS_AF_LINK', 1,
description : 'getifaddrs() and AF_LINK is available')
endif
setcap_prog = find_program('setcap', '/usr/sbin/setcap', '/sbin/setcap', required : false)
cap_dep = dependency('libcap', required: false)
# user/group to change to in gst-ptp-helper
ptp_helper_setuid_user = get_option('ptp-helper-setuid-user')
if ptp_helper_setuid_user != ''
cdata.set_quoted('HAVE_PTP_HELPER_SETUID_USER', ptp_helper_setuid_user,
description : 'PTP helper setuid user')
endif
ptp_helper_setuid_group = get_option('ptp-helper-setuid-group')
if ptp_helper_setuid_group != ''
cdata.set_quoted('HAVE_PTP_HELPER_SETUID_GROUP', ptp_helper_setuid_group,
description : 'PTP helper setuid group')
endif
# how to install gst-ptp-helper
with_ptp_helper_permissions = get_option('ptp-helper-permissions')
if with_ptp_helper_permissions == 'auto'
if setcap_prog.found() and cap_dep.found()
with_ptp_helper_permissions = 'capabilities'
else
with_ptp_helper_permissions = 'setuid-root'
endif
endif
message('How to install gst-ptp-helper: ' + with_ptp_helper_permissions)
if with_ptp_helper_permissions == 'none'
# nothing to do
elif with_ptp_helper_permissions == 'setuid-root'
cdata.set('HAVE_PTP_HELPER_SETUID', 1,
description : 'Use setuid-root for permissions in PTP helper')
elif with_ptp_helper_permissions == 'capabilities'
if not setcap_prog.found()
error('capabilities-based ptp-helper-permissions requested, but could not find setcap tool.')
elif not cap_dep.found()
error('capabilities-based ptp-helper-permissions requested, but could not find libcap.')
endif
cdata.set('HAVE_PTP_HELPER_CAPABILITIES', 1,
description : 'Use capabilities for permissions in PTP helper')
else
error('Unexpected ptp helper permissions value: ' + with_ptp_helper_permissions)
endif
exe = executable('gst-ptp-helper', 'main.rs',
override_options : ['rust_std=2018'],
rust_args : ['-Cpanic=abort'],
dependencies : [cap_dep],
install_dir : helpers_install_dir,
install : true)
meson.add_install_script('ptp_helper_post_install.sh',
helpers_install_dir, with_ptp_helper_permissions,
setcap_prog.found() ? setcap_prog.full_path() : '')
meson.add_devenv({'GST_PTP_HELPER': exe.full_path()})
endif
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment