summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian@centricular.com>2023-02-04 00:55:39 +0200
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>2023-04-07 15:49:02 +0000
commit3fdfcdf2f60012868c65cb182eb3013918ff33a5 (patch)
tree5cf05289c6f2cd451f49a34a28dc8e15de3b07d1
parent8ed0b03c780c407750234df9bd875c8348892efd (diff)
downloadgstreamer-3fdfcdf2f60012868c65cb182eb3013918ff33a5.tar.gz
ptp-helper: Rewrite in Rust for portability and security
This works on Linux, Android, Windows, macOS, FreeBSD, NetBSD, OpenBSD, DragonFlyBSD, Solaris and Illumos. Newly supported compared to the C version is Windows. Compared to the C version various error paths are handled more correctly and a couple of memory leaks are fixed. Otherwise it should work identically. The minimum required Rust version for compiling this is 1.48, i.e. the version currently in Debian stable. On Windows, Rust 1.54 is needed at least. Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1259 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3889>
-rwxr-xr-xgst-env.py2
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c687
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/meson.build103
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/args.rs66
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in2
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/error.rs219
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs786
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/io.rs769
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/main.rs232
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/meson.build91
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/net.rs667
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs169
-rwxr-xr-xsubprojects/gstreamer/libs/gst/helpers/ptp/ptp_helper_post_install.sh (renamed from subprojects/gstreamer/libs/gst/helpers/ptp_helper_post_install.sh)0
-rw-r--r--subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs150
14 files changed, 3154 insertions, 789 deletions
diff --git a/gst-env.py b/gst-env.py
index 2a714c28e9..2d4ae32a43 100755
--- a/gst-env.py
+++ b/gst-env.py
@@ -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'
diff --git a/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c b/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c
deleted file mode 100644
index 4e8ab3624f..0000000000
--- a/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c
+++ /dev/null
@@ -1,687 +0,0 @@
-/* GStreamer
- * Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
- *
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-/* 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
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <string.h>
-
-#ifdef HAVE_GETIFADDRS_AF_LINK
-#include <ifaddrs.h>
-#include <net/if_dl.h>
-#endif
-
-#ifdef HAVE_PTP_HELPER_SETUID
-#include <grp.h>
-#include <pwd.h>
-#endif
-
-#ifdef HAVE_PTP_HELPER_CAPABILITIES
-#include <sys/capability.h>
-#endif
-
-#include <glib.h>
-#include <gio/gio.h>
-
-#include <gst/gst.h>
-#include <gst/net/gstptp_private.h>
-
-#define PTP_MULTICAST_GROUP "224.0.1.129"
-#define PTP_EVENT_PORT 319
-#define PTP_GENERAL_PORT 320
-
-static gchar **ifaces = NULL;
-static gboolean verbose = FALSE;
-static guint64 clock_id = (guint64) - 1;
-static guint8 clock_id_array[8];
-
-static GOptionEntry opt_entries[] = {
- {"interface", 'i', 0, G_OPTION_ARG_STRING_ARRAY, &ifaces,
- "Interface to listen on", NULL},
- {"clock-id", 'c', 0, G_OPTION_ARG_INT64, &clock_id,
- "PTP clock id", NULL},
- {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
- "Be verbose", NULL},
- {NULL}
-};
-
-static GSocketAddress *event_saddr, *general_saddr;
-static GSocket *socket_event, *socket_general;
-static GIOChannel *stdin_channel, *stdout_channel;
-
-static gboolean
-have_socket_data_cb (GSocket * socket, GIOCondition condition,
- gpointer user_data)
-{
- gchar buffer[8192];
- gssize read;
- gsize written;
- GError *err = NULL;
- GIOStatus status;
- StdIOHeader header = { 0, };
-
- read = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &err);
- if (read == -1)
- g_error ("Failed to read from socket: %s", err->message);
- g_clear_error (&err);
-
- if (verbose)
- g_message ("Received %" G_GSSIZE_FORMAT " bytes from %s socket", read,
- (socket == socket_event ? "event" : "general"));
-
- header.size = read;
- header.type = (socket == socket_event) ? TYPE_EVENT : TYPE_GENERAL;
-
- status =
- g_io_channel_write_chars (stdout_channel, (gchar *) & header,
- sizeof (header), &written, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to write to stdout: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdout");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdout write status: %d", status);
- } else if (written != sizeof (header)) {
- g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
- }
-
- status =
- g_io_channel_write_chars (stdout_channel, buffer, read, &written, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to write to stdout: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdout");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdout write status: %d", status);
- } else if (written != read) {
- g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
- }
-
- return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-have_stdin_data_cb (GIOChannel * channel, GIOCondition condition,
- gpointer user_data)
-{
- GIOStatus status;
- StdIOHeader header = { 0, };
- gchar buffer[8192];
- GError *err = NULL;
- gsize read;
- gssize written;
-
- if ((condition & G_IO_STATUS_EOF)) {
- g_message ("EOF on stdin");
- exit (0);
- }
-
- status =
- g_io_channel_read_chars (channel, (gchar *) & header, sizeof (header),
- &read, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to read from stdin: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdin");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdin read status: %d", status);
- } else if (read != sizeof (header)) {
- g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read);
- } else if (header.size > 8192) {
- g_error ("Unexpected size: %u", header.size);
- }
-
- status = g_io_channel_read_chars (channel, buffer, header.size, &read, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to read from stdin: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdin");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdin read status: %d", status);
- } else if (read != header.size) {
- g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read);
- }
-
- switch (header.type) {
- case TYPE_EVENT:
- case TYPE_GENERAL:
- written =
- g_socket_send_to (header.type ==
- TYPE_EVENT ? socket_event : socket_general,
- (header.type == TYPE_EVENT ? event_saddr : general_saddr), buffer,
- header.size, NULL, &err);
- if (written == -1)
- g_error ("Failed to write to socket: %s", err->message);
- else if (written != header.size)
- g_error ("Unexpected write size: %" G_GSSIZE_FORMAT, written);
- g_clear_error (&err);
- if (verbose)
- g_message ("Sent %" G_GSSIZE_FORMAT " bytes to %s socket", read,
- (header.type == TYPE_EVENT ? "event" : "general"));
- break;
- default:
- break;
- }
-
- return G_SOURCE_CONTINUE;
-}
-
-static void
-setup_sockets (void)
-{
- GInetAddress *bind_addr, *mcast_addr;
- GSocketAddress *bind_saddr;
- GSource *socket_event_source, *socket_general_source;
- gchar **probed_ifaces = NULL;
- GError *err = NULL;
-
- /* Create sockets */
- socket_event =
- g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
- G_SOCKET_PROTOCOL_UDP, &err);
- if (!socket_event)
- g_error ("Couldn't create event socket: %s", err->message);
- g_clear_error (&err);
-
- socket_general =
- g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
- G_SOCKET_PROTOCOL_UDP, &err);
- if (!socket_general)
- g_error ("Couldn't create general socket: %s", err->message);
- g_clear_error (&err);
-
- /* Bind sockets */
- bind_addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
- bind_saddr = g_inet_socket_address_new (bind_addr, PTP_EVENT_PORT);
- if (!g_socket_bind (socket_event, bind_saddr, TRUE, &err))
- g_error ("Couldn't bind event socket: %s", err->message);
- g_object_unref (bind_saddr);
- bind_saddr = g_inet_socket_address_new (bind_addr, PTP_GENERAL_PORT);
- if (!g_socket_bind (socket_general, bind_saddr, TRUE, &err))
- g_error ("Couldn't bind general socket: %s", err->message);
- g_object_unref (bind_saddr);
- g_object_unref (bind_addr);
-
- /* Probe all non-loopback interfaces */
- if (!ifaces) {
-#if defined(HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR)
- struct ifreq ifr;
- struct ifconf ifc;
- gchar buf[8192];
-
- ifc.ifc_len = sizeof (buf);
- ifc.ifc_buf = buf;
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) {
- guint i, idx = 0;
-
- probed_ifaces = g_new0 (gchar *, ifc.ifc_len + 1);
-
- for (i = 0; i < ifc.ifc_len / sizeof (struct ifreq); i++) {
- strncpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name, IFNAMSIZ);
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) {
- if ((ifr.ifr_flags & IFF_LOOPBACK))
- continue;
- probed_ifaces[idx] = g_strndup (ifc.ifc_req[i].ifr_name, IFNAMSIZ);
- idx++;
- } else {
- g_warning ("can't get flags of interface '%s'",
- ifc.ifc_req[i].ifr_name);
- probed_ifaces[idx] = g_strndup (ifc.ifc_req[i].ifr_name, IFNAMSIZ);
- idx++;
- }
- if (idx != 0)
- ifaces = probed_ifaces;
- }
- }
-#elif defined(HAVE_GETIFADDRS_AF_LINK)
- struct ifaddrs *ifaddr, *ifa;
-
- if (getifaddrs (&ifaddr) != -1) {
- GPtrArray *arr;
-
- arr = g_ptr_array_new ();
-
- for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
- if ((ifa->ifa_flags & IFF_LOOPBACK))
- continue;
-
- if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK)
- continue;
-
- g_ptr_array_add (arr, g_strdup (ifa->ifa_name));
- }
- freeifaddrs (ifaddr);
-
- g_ptr_array_add (arr, NULL);
- ifaces = probed_ifaces = (gchar **) g_ptr_array_free (arr, FALSE);
- }
-#else
-#warning "Implement something to list all network interfaces"
-#endif
- }
-
- /* Get a clock id from the MAC address if none was given */
- if (clock_id == (guint64) - 1) {
- gboolean success = FALSE;
-
-#if defined(HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR)
- struct ifreq ifr;
-
- if (ifaces) {
- gchar **ptr = ifaces;
-
- while (*ptr) {
- memcpy (ifr.ifr_name, *ptr, IFNAMSIZ);
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR, &ifr) == 0) {
- clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0];
- clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1];
- clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2];
- clock_id_array[3] = 0xff;
- clock_id_array[4] = 0xfe;
- clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3];
- clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4];
- clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5];
- success = TRUE;
- break;
- }
- }
-
- ptr++;
- } else {
- struct ifconf ifc;
- gchar buf[8192];
-
- ifc.ifc_len = sizeof (buf);
- ifc.ifc_buf = buf;
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) {
- guint i;
-
- for (i = 0; i < ifc.ifc_len / sizeof (struct ifreq); i++) {
- strncpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name, IFNAMSIZ);
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) {
- if ((ifr.ifr_flags & IFF_LOOPBACK))
- continue;
-
- if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR,
- &ifr) == 0) {
- clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0];
- clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1];
- clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2];
- clock_id_array[3] = 0xff;
- clock_id_array[4] = 0xfe;
- clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3];
- clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4];
- clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5];
- success = TRUE;
- break;
- }
- } else {
- g_warning ("can't get flags of interface '%s'",
- ifc.ifc_req[i].ifr_name);
- }
- }
- }
- }
-#elif defined(HAVE_GETIFADDRS_AF_LINK)
- struct ifaddrs *ifaddr, *ifa;
-
- if (getifaddrs (&ifaddr) != -1) {
- for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
- struct sockaddr_dl *sdl = (struct sockaddr_dl *) ifa->ifa_addr;
- guint8 mac_addr[6];
-
- if ((ifa->ifa_flags & IFF_LOOPBACK))
- continue;
-
- if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK)
- continue;
-
- if (ifaces) {
- gchar **p = ifaces;
- gboolean found = FALSE;
-
- while (*p) {
- if (strcmp (*p, ifa->ifa_name) == 0) {
- found = TRUE;
- break;
- }
- p++;
- }
-
- if (!found)
- continue;
- }
-
- if (sdl->sdl_alen != 6)
- continue;
-
- memcpy (mac_addr, LLADDR (sdl), sdl->sdl_alen);
-
- clock_id_array[0] = mac_addr[0];
- clock_id_array[1] = mac_addr[1];
- clock_id_array[2] = mac_addr[2];
- clock_id_array[3] = 0xff;
- clock_id_array[4] = 0xfe;
- clock_id_array[5] = mac_addr[3];
- clock_id_array[6] = mac_addr[4];
- clock_id_array[7] = mac_addr[5];
- success = TRUE;
- break;
- }
-
- freeifaddrs (ifaddr);
- }
-#else
-#warning "Implement something to get MAC addresses of network interfaces"
-#endif
-
- if (!success) {
- g_warning ("can't get any MAC address, using random clock id");
- clock_id = (((guint64) g_random_int ()) << 32) | (g_random_int ());
- GST_WRITE_UINT64_BE (clock_id_array, clock_id);
- clock_id_array[3] = 0xff;
- clock_id_array[4] = 0xfe;
- }
- } else {
- GST_WRITE_UINT64_BE (clock_id_array, clock_id);
- }
-
- /* Join multicast groups */
- mcast_addr = g_inet_address_new_from_string (PTP_MULTICAST_GROUP);
- if (ifaces) {
- gchar **ptr = ifaces;
- gboolean success = FALSE;
-
- while (*ptr) {
- gint c = 0;
- if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, *ptr,
- &err)
- && !g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE))
- g_warning ("Couldn't join multicast group on interface '%s': %s", *ptr,
- err->message);
- else
- c++;
- g_clear_error (&err);
-
- if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE,
- *ptr, &err)
- && !g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE))
- g_warning ("Couldn't join multicast group on interface '%s': %s", *ptr,
- err->message);
- else
- c++;
- g_clear_error (&err);
-
- if (c == 2)
- success = TRUE;
- ptr++;
- }
-
- if (!success) {
- /* Join multicast group without any interface */
- if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL,
- &err))
- g_error ("Couldn't join multicast group: %s", err->message);
- g_clear_error (&err);
- if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE,
- NULL, &err))
- g_error ("Couldn't join multicast group: %s", err->message);
- g_clear_error (&err);
- }
- } else {
- /* Join multicast group without any interface */
- if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL,
- &err))
- g_error ("Couldn't join multicast group: %s", err->message);
- g_clear_error (&err);
- if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE, NULL,
- &err))
- g_error ("Couldn't join multicast group: %s", err->message);
- g_clear_error (&err);
- }
-
- event_saddr = g_inet_socket_address_new (mcast_addr, PTP_EVENT_PORT);
- general_saddr = g_inet_socket_address_new (mcast_addr, PTP_GENERAL_PORT);
-
- /* Create socket sources */
- socket_event_source =
- g_socket_create_source (socket_event, G_IO_IN | G_IO_PRI, NULL);
- g_source_set_priority (socket_event_source, G_PRIORITY_HIGH);
- g_source_set_callback (socket_event_source, (GSourceFunc) have_socket_data_cb,
- NULL, NULL);
- g_source_attach (socket_event_source, NULL);
- socket_general_source =
- g_socket_create_source (socket_general, G_IO_IN | G_IO_PRI, NULL);
- g_source_set_priority (socket_general_source, G_PRIORITY_DEFAULT);
- g_source_set_callback (socket_general_source,
- (GSourceFunc) have_socket_data_cb, NULL, NULL);
- g_source_attach (socket_general_source, NULL);
-
- g_strfreev (probed_ifaces);
-}
-
-static void
-drop_privileges (void)
-{
-#ifdef HAVE_PTP_HELPER_SETUID
- /* Switch to the given user/group */
-#ifdef HAVE_PTP_HELPER_SETUID_GROUP
- {
- struct group *grp;
-
- grp = getgrnam (HAVE_PTP_HELPER_SETUID_GROUP);
- if (!grp)
- g_error ("Failed to get group information '%s': %s",
- HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno));
-
- if (setgid (grp->gr_gid) != 0)
- g_error ("Failed to change to group '%s': %s",
- HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno));
- }
-#endif
-
-#ifdef HAVE_PTP_HELPER_SETUID_USER
- {
- struct passwd *pwd;
-
- pwd = getpwnam (HAVE_PTP_HELPER_SETUID_USER);
- if (!pwd)
- g_error ("Failed to get user information '%s': %s",
- HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno));
-
-#ifndef HAVE_PTP_HELPER_SETUID_GROUP
- if (setgid (pwd->pw_gid) != 0)
- g_error ("Failed to change to user group '%s': %s",
- HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno));
-#endif
-
- if (setuid (pwd->pw_uid) != 0)
- g_error ("Failed to change to user '%s': %s", HAVE_PTP_HELPER_SETUID_USER,
- g_strerror (errno));
- }
-#endif
-#endif
-#ifdef HAVE_PTP_HELPER_CAPABILITIES
- /* Drop all capabilities */
- {
- cap_t caps;
-
- caps = cap_get_proc ();
- if (caps == 0)
- g_error ("Failed to get process caps: %s", g_strerror (errno));
- if (cap_clear (caps) != 0)
- g_error ("Failed to clear caps: %s", g_strerror (errno));
- if (cap_set_proc (caps) != 0)
- g_error ("Failed to set process caps: %s", g_strerror (errno));
- }
-#endif
-}
-
-static void
-setup_stdio_channels (void)
-{
- GSource *stdin_source;
-
- /* Create stdin source */
- stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
- if (g_io_channel_set_encoding (stdin_channel, NULL,
- NULL) == G_IO_STATUS_ERROR)
- g_error ("Failed to set stdin to binary encoding");
- g_io_channel_set_buffered (stdin_channel, FALSE);
- stdin_source =
- g_io_create_watch (stdin_channel, G_IO_IN | G_IO_PRI | G_IO_HUP);
- g_source_set_priority (stdin_source, G_PRIORITY_DEFAULT);
- g_source_set_callback (stdin_source, (GSourceFunc) have_stdin_data_cb, NULL,
- NULL);
- g_source_attach (stdin_source, NULL);
-
- /* Create stdout channel */
- stdout_channel = g_io_channel_unix_new (STDOUT_FILENO);
- if (g_io_channel_set_encoding (stdout_channel, NULL,
- NULL) == G_IO_STATUS_ERROR)
- g_error ("Failed to set stdout to binary encoding");
- g_io_channel_set_buffered (stdout_channel, FALSE);
-}
-
-static void
-write_clock_id (void)
-{
- GError *err = NULL;
- GIOStatus status;
- StdIOHeader header = { 0, };
- gsize written;
-
- /* Write clock id to stdout */
-
- header.type = TYPE_CLOCK_ID;
- header.size = 8;
- status =
- g_io_channel_write_chars (stdout_channel, (gchar *) & header,
- sizeof (header), &written, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to write to stdout: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdout");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdout write status: %d", status);
- } else if (written != sizeof (header)) {
- g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
- }
-
- status =
- g_io_channel_write_chars (stdout_channel,
- (const gchar *) clock_id_array, sizeof (clock_id_array), &written, &err);
- if (status == G_IO_STATUS_ERROR) {
- g_error ("Failed to write to stdout: %s", err->message);
- g_clear_error (&err);
- } else if (status == G_IO_STATUS_EOF) {
- g_message ("EOF on stdout");
- exit (0);
- } else if (status != G_IO_STATUS_NORMAL) {
- g_error ("Unexpected stdout write status: %d", status);
- } else if (written != sizeof (clock_id_array)) {
- g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written);
- }
-}
-
-#ifdef __APPLE__
-static gint
-dummy_poll (GPollFD * fds, guint nfds, gint timeout)
-{
- return g_poll (fds, nfds, timeout);
-}
-#endif
-
-gint
-main (gint argc, gchar ** argv)
-{
- GOptionContext *opt_ctx;
- GMainLoop *loop;
- GError *err = NULL;
-
- /* FIXME: Work around some side effects of the changes from
- * https://bugzilla.gnome.org/show_bug.cgi?id=741054
- *
- * The modified poll function somehow calls setugid(), which
- * then abort()s the application. Make sure that we use g_poll()
- * here!
- */
-#ifdef __APPLE__
- {
- GMainContext *context = g_main_context_default ();
- g_main_context_set_poll_func (context, dummy_poll);
- }
-#endif
-
-#ifdef HAVE_PTP_HELPER_SETUID
- if (setuid (0) < 0)
- g_error ("not running with superuser privileges");
-#endif
-
- opt_ctx = g_option_context_new ("- GStreamer PTP helper process");
- g_option_context_add_main_entries (opt_ctx, opt_entries, NULL);
- if (!g_option_context_parse (opt_ctx, &argc, &argv, &err))
- g_error ("Error parsing options: %s", err->message);
- g_clear_error (&err);
- g_option_context_free (opt_ctx);
-
- setup_sockets ();
- drop_privileges ();
- setup_stdio_channels ();
- write_clock_id ();
-
- /* Get running */
- loop = g_main_loop_new (NULL, FALSE);
- g_main_loop_run (loop);
-
- /* We never exit cleanly, so don't do cleanup */
- g_assert_not_reached ();
-
- return 0;
-}
diff --git a/subprojects/gstreamer/libs/gst/helpers/meson.build b/subprojects/gstreamer/libs/gst/helpers/meson.build
index 543064ca13..7346e4de0a 100644
--- a/subprojects/gstreamer/libs/gst/helpers/meson.build
+++ b/subprojects/gstreamer/libs/gst/helpers/meson.build
@@ -1,3 +1,5 @@
+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')
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs
new file mode 100644
index 0000000000..75868b9989
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs
@@ -0,0 +1,66 @@
+// 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
+
+use std::env;
+
+use crate::{
+ bail,
+ error::{Context, Error},
+};
+
+/// Parsed command-line arguments.
+#[derive(Debug)]
+pub struct Args {
+ pub interfaces: Vec<String>,
+ pub verbose: bool,
+ pub clock_id: u64,
+}
+
+/// Parse the command-line arguments.
+pub fn parse_args() -> Result<Args, Error> {
+ 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().context("No interface following -i")?;
+ interfaces.push(iface);
+ }
+ "-c" | "--clock-id" => {
+ let clock_id_arg = args.next().context("No clock-id following -c")?;
+ clock_id = clock_id_arg.parse::<u64>().context("Invalid clock ID")?;
+ }
+ arg => {
+ bail!("Unknown command-line argument {}", arg);
+ }
+ }
+ }
+
+ let args = Args {
+ interfaces,
+ verbose,
+ clock_id,
+ };
+
+ if verbose {
+ eprintln!("Running with arguments {:#?}", args);
+ }
+
+ Ok(args)
+}
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in b/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in
new file mode 100644
index 0000000000..039b153c4b
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in
@@ -0,0 +1,2 @@
+pub const PTP_HELPER_SETUID_USER: Option<&str> = @PTP_HELPER_SETUID_USER@;
+pub const PTP_HELPER_SETUID_GROUP: Option<&str> = @PTP_HELPER_SETUID_GROUP@;
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs
new file mode 100644
index 0000000000..8a8218f2cf
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs
@@ -0,0 +1,219 @@
+// 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
+
+use std::{
+ error::Error as StdError,
+ fmt::{Debug, Display},
+};
+
+/// Custom error type for error display reasons.
+pub struct Error(Box<ErrorInner>);
+
+impl Error {
+ #[doc(hidden)]
+ pub fn new(message: String, source: Option<Box<dyn StdError + 'static>>) -> Self {
+ Error(Box::new(ErrorInner { message, source }))
+ }
+}
+
+struct ErrorInner {
+ message: String,
+ source: Option<Box<dyn StdError + 'static>>,
+}
+
+impl Debug for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut e = self;
+ let mut first = true;
+
+ // Print the actual error message of this error and then iterate over the whole
+ // error chain and print each cause on the chain indented.
+ 'next_error: loop {
+ if first {
+ writeln!(f, "{}", e.0.message)?;
+ first = false;
+ } else {
+ for line in e.0.message.lines() {
+ writeln!(f, " {}", line)?;
+ }
+ }
+
+ let mut source = match e.0.source {
+ Some(ref source) => &**source,
+ None => break 'next_error,
+ };
+
+ if let Some(source) = source.downcast_ref::<Error>() {
+ e = source;
+ writeln!(f, "\nCaused by:\n")?;
+ continue 'next_error;
+ }
+
+ loop {
+ writeln!(f, "\nCaused by:\n")?;
+ let source_str = source.to_string();
+ for line in source_str.lines() {
+ writeln!(f, " {}", line)?;
+ }
+
+ source = match source.source() {
+ None => break 'next_error,
+ Some(source) => source,
+ };
+
+ if let Some(source) = source.downcast_ref::<Error>() {
+ e = source;
+ writeln!(f, "\nCaused by:\n")?;
+ continue 'next_error;
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ <Self as Debug>::fmt(self, f)
+ }
+}
+
+impl StdError for Error {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ match self.0.source {
+ None => None,
+ Some(ref source) => Some(&**source),
+ }
+ }
+}
+
+impl<'a> From<&'a str> for Error {
+ fn from(message: &'a str) -> Self {
+ Error(Box::new(ErrorInner {
+ message: String::from(message),
+ source: None,
+ }))
+ }
+}
+
+impl From<String> for Error {
+ fn from(message: String) -> Self {
+ Error(Box::new(ErrorInner {
+ message,
+ source: None,
+ }))
+ }
+}
+
+#[macro_export]
+/// Create a new `Error` from the given message and possibly source error.
+macro_rules! format_err {
+ (source: $source:expr, $msg:literal $(,)?) => {
+ $crate::error::Error::new(
+ String::from($msg),
+ Some($source.into()),
+ )
+ };
+ (source: $source:expr, $err:expr $(,)?) => {
+ $crate::error::Error::new(
+ format!($err),
+ Some($source.into()),
+ )
+ };
+ (source: $source:expr, $fmt:expr, $($arg:tt)*) => {
+ $crate::error::Error::new(
+ format!($fmt, $($arg)*),
+ Some($source.into()),
+ )
+ };
+
+ ($msg:literal $(,)?) => {
+ $crate::error::Error::new(
+ String::from($msg),
+ None,
+ )
+ };
+ ($err:expr $(,)?) => {
+ $crate::error::Error::new(
+ format!($err),
+ None,
+ )
+ };
+ ($fmt:expr, $($arg:tt)*) => {
+ $crate::error::Error::new(
+ format!($fmt, $($arg)*),
+ None,
+ )
+ };
+}
+
+#[macro_export]
+/// Return new `Error` from the given message and possibly source error.
+macro_rules! bail {
+ ($($arg:tt)+) => {
+ return Err($crate::format_err!($($arg)+));
+ };
+}
+
+/// Trait for adding a context message to any `Result<T, E>` or `Option<T>`
+/// and turning it into a `Result<T, Error>`.
+pub trait Context<T, E> {
+ /// Add a static context.
+ ///
+ /// This should only be called if `context` requires no allocations or otherwise
+ /// exists already.
+ fn context<C>(self, context: C) -> Result<T, Error>
+ where
+ C: Display;
+
+ /// Add a lazily created context.
+ fn with_context<C, F>(self, func: F) -> Result<T, Error>
+ where
+ C: Display,
+ F: FnOnce() -> C;
+}
+
+impl<T, E> Context<T, E> for Result<T, E>
+where
+ E: StdError + 'static,
+{
+ fn context<C>(self, context: C) -> Result<T, Error>
+ where
+ C: Display,
+ {
+ self.map_err(|err| Error::new(context.to_string(), Some(Box::new(err))))
+ }
+
+ fn with_context<C, F>(self, func: F) -> Result<T, Error>
+ where
+ C: Display,
+ F: FnOnce() -> C,
+ {
+ self.map_err(|err| Error::new(func().to_string(), Some(Box::new(err))))
+ }
+}
+
+impl<T> Context<T, Error> for Option<T> {
+ fn context<C>(self, context: C) -> Result<T, Error>
+ where
+ C: Display,
+ {
+ self.ok_or_else(|| Error::new(context.to_string(), None))
+ }
+
+ fn with_context<C, F>(self, func: F) -> Result<T, Error>
+ where
+ C: Display,
+ F: FnOnce() -> C,
+ {
+ self.ok_or_else(|| Error::new(func().to_string(), None))
+ }
+}
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs
new file mode 100644
index 0000000000..5379813465
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs
@@ -0,0 +1,786 @@
+// 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
+
+#[cfg(unix)]
+#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
+pub mod unix {
+ use std::os::{raw::*, unix::io::RawFd};
+
+ #[cfg(not(any(
+ target_os = "linux",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ target_os = "solaris",
+ target_os = "illumos",
+ )))]
+ compile_error!("Unsupported Operating System");
+
+ // The API definitions below are taken from the libc crate version 0.2.139, the corresponding C
+ // headers, manpages and related documentation.
+ //
+ // XXX: Once meson has cargo subproject support all of the below can be replaced with the libc crate.
+ pub const STDIN_FILENO: RawFd = 0;
+ pub const STDOUT_FILENO: RawFd = 1;
+ pub const O_RDONLY: c_int = 0;
+
+ pub const POLLIN: c_short = 0x1;
+ pub const POLLERR: c_short = 0x8;
+ pub const POLLHUP: c_short = 0x10;
+ pub const POLLNVAL: c_short = 0x20;
+
+ pub const IPPROTO_IP: c_int = 0;
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const IP_ADD_MEMBERSHIP: c_int = 12;
+ #[cfg(target_os = "linux")]
+ pub const IP_ADD_MEMBERSHIP: c_int = 35;
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ pub const IP_ADD_MEMBERSHIP: c_int = 19;
+
+ #[cfg(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const SOL_SOCKET: c_int = 0xffff;
+ #[cfg(all(
+ target_os = "linux",
+ any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ )
+ ))]
+ pub const SOL_SOCKET: c_int = 0xffff;
+ #[cfg(all(
+ target_os = "linux",
+ not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ )),
+ ))]
+ pub const SOL_SOCKET: c_int = 1;
+
+ #[cfg(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const SO_REUSEADDR: c_int = 0x4;
+ #[cfg(all(
+ target_os = "linux",
+ any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ ),
+ ))]
+ pub const SO_REUSEADDR: c_int = 0x4;
+ #[cfg(all(
+ target_os = "linux",
+ not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ )),
+ ))]
+ pub const SO_REUSEADDR: c_int = 2;
+
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const SO_REUSEPORT: c_int = 0x200;
+ #[cfg(all(
+ target_os = "linux",
+ any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ ),
+ ))]
+ pub const SO_REUSEPORT: c_int = 0x200;
+ #[cfg(all(
+ target_os = "linux",
+ not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "mips",
+ target_arch = "mips64"
+ )),
+ ))]
+ pub const SO_REUSEPORT: c_int = 15;
+
+ pub const AF_INET: c_int = 2;
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const AF_LINK: c_int = 18;
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ pub const AF_LINK: c_int = 25;
+ #[cfg(target_os = "linux")]
+ pub const AF_PACKET: c_int = 17;
+
+ pub const IFF_UP: c_int = 0x1;
+ pub const IFF_LOOPBACK: c_int = 0x8;
+
+ #[cfg(target_os = "linux")]
+ pub const IFF_MULTICAST: c_int = 0x1000;
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ pub const IFF_MULTICAST: ::c_int = 0x0800;
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ pub const IFF_MULTICAST: c_int = 0x08000;
+
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ pub const IF_NAMESIZE: usize = 32;
+ #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos")))]
+ pub const IF_NAMESIZE: usize = 16;
+
+ extern "C" {
+ #[cfg_attr(
+ all(target_os = "macos", target_arch = "x86"),
+ link_name = "open$UNIX2003"
+ )]
+ pub fn open(path: *const u8, oflag: c_int, ...) -> i32;
+ #[cfg_attr(
+ all(target_os = "macos", target_arch = "x86"),
+ link_name = "read$UNIX2003"
+ )]
+ pub fn read(fd: RawFd, buf: *mut u8, count: usize) -> isize;
+ #[cfg_attr(
+ all(target_os = "macos", target_arch = "x86"),
+ link_name = "write$UNIX2003"
+ )]
+ pub fn write(fd: RawFd, buf: *const u8, count: usize) -> isize;
+
+ #[cfg_attr(
+ all(target_os = "macos", target_arch = "x86"),
+ link_name = "close$UNIX2003"
+ )]
+ pub fn close(fd: c_int) -> c_int;
+
+ #[cfg_attr(
+ all(target_os = "macos", target_arch = "x86"),
+ link_name = "poll$UNIX2003"
+ )]
+ pub fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int;
+
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ pub fn if_nametoindex(name: *const c_char) -> c_uint;
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ pub fn setsockopt(
+ socket: c_int,
+ level: c_int,
+ name: c_int,
+ value: *const c_void,
+ option_len: u32,
+ ) -> c_int;
+
+ pub fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int;
+ pub fn freeifaddrs(ifa: *mut ifaddrs);
+ }
+
+ #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))]
+ pub type nfds_t = c_ulong;
+ #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos")))]
+ pub type nfds_t = c_uint;
+
+ #[repr(C)]
+ #[derive(Clone, Copy)]
+ pub struct pollfd {
+ pub fd: c_int,
+ pub events: c_short,
+ pub revents: c_short,
+ }
+
+ pub type in_addr_t = u32;
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct in_addr {
+ pub s_addr: in_addr_t,
+ }
+
+ // Solaris does not have support for this so we fall back to use the std API
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct ip_mreqn {
+ pub imr_multiaddr: in_addr,
+ pub imr_address: in_addr,
+ pub imr_ifindex: c_int,
+ }
+
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ #[repr(C)]
+ pub struct ifaddrs {
+ pub ifa_next: *mut ifaddrs,
+ pub ifa_name: *mut c_char,
+ pub ifa_flags: c_ulong,
+ pub ifa_addr: *mut sockaddr,
+ pub ifa_netmask: *mut sockaddr,
+ pub ifa_dstaddr: *mut sockaddr,
+ pub ifa_data: *mut c_void,
+ }
+
+ #[cfg(target_os = "linux")]
+ #[repr(C)]
+ pub struct ifaddrs {
+ pub ifa_next: *mut ifaddrs,
+ pub ifa_name: *mut c_char,
+ pub ifa_flags: c_uint,
+ pub ifa_addr: *mut sockaddr,
+ pub ifa_netmask: *mut sockaddr,
+ pub ifa_ifu: *mut sockaddr,
+ pub ifa_data: *mut c_void,
+ }
+
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ #[repr(C)]
+ pub struct ifaddrs {
+ pub ifa_next: *mut ifaddrs,
+ pub ifa_name: *mut c_char,
+ pub ifa_flags: c_uint,
+ pub ifa_addr: *mut sockaddr,
+ pub ifa_netmask: *mut sockaddr,
+ pub ifa_dstaddr: *mut sockaddr,
+ pub ifa_data: *mut c_void,
+ #[cfg(target_os = "netbsd")]
+ pub ifa_addrflags: c_uint,
+ }
+
+ #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))]
+ #[repr(C)]
+ pub struct sockaddr {
+ pub sa_family: u16,
+ pub sa_data: [u8; 14],
+ }
+
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ #[repr(C)]
+ pub struct sockaddr {
+ pub sa_len: u8,
+ pub sa_family: u8,
+ pub sa_data: [u8; 14],
+ }
+
+ #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))]
+ #[repr(C)]
+ pub struct sockaddr_in {
+ pub sin_family: u16,
+ pub sin_port: u16,
+ pub sin_addr: in_addr,
+ pub sin_zero: [u8; 8],
+ }
+ #[cfg(any(
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+ target_os = "macos",
+ ))]
+ #[repr(C)]
+ pub struct sockaddr_in {
+ pub sin_len: u8,
+ pub sin_family: u8,
+ pub sin_port: u16,
+ pub sin_addr: in_addr,
+ pub sin_zero: [u8; 8],
+ }
+
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ #[repr(C)]
+ pub struct sockaddr_dl {
+ pub sdl_family: u16,
+ pub sdl_index: u16,
+ pub sdl_type: u8,
+ pub sdl_nlen: u8,
+ pub sdl_alen: u8,
+ pub sdl_slen: u8,
+ pub sdl_data: [u8; 244],
+ }
+
+ #[cfg(any(target_os = "netbsd", target_os = "macos"))]
+ #[repr(C)]
+ pub struct sockaddr_dl {
+ pub sdl_len: u8,
+ pub sdl_family: u8,
+ pub sdl_index: u16,
+ pub sdl_type: u8,
+ pub sdl_nlen: u8,
+ pub sdl_alen: u8,
+ pub sdl_slen: u8,
+ pub sdl_data: [u8; 12],
+ }
+
+ #[cfg(target_os = "openbsd")]
+ #[repr(C)]
+ pub struct sockaddr_dl {
+ pub sdl_len: u8,
+ pub sdl_family: u8,
+ pub sdl_index: u16,
+ pub sdl_type: u8,
+ pub sdl_nlen: u8,
+ pub sdl_alen: u8,
+ pub sdl_slen: u8,
+ pub sdl_data: [u8; 24],
+ }
+
+ #[cfg(target_os = "freebsd")]
+ #[repr(C)]
+ pub struct sockaddr_dl {
+ pub sdl_len: u8,
+ pub sdl_family: u8,
+ pub sdl_index: u16,
+ pub sdl_type: u8,
+ pub sdl_nlen: u8,
+ pub sdl_alen: u8,
+ pub sdl_slen: u8,
+ pub sdl_data: [u8; 46],
+ }
+
+ #[cfg(target_os = "dragonfly")]
+ #[repr(C)]
+ pub struct sockaddr_dl {
+ pub sdl_len: u8,
+ pub sdl_family: u8,
+ pub sdl_index: u16,
+ pub sdl_type: u8,
+ pub sdl_nlen: u8,
+ pub sdl_alen: u8,
+ pub sdl_slen: u8,
+ pub sdl_data: [u8; 12],
+ pub sdl_rcf: u16,
+ pub sdl_route: [u8; 16],
+ }
+
+ #[cfg(target_os = "linux")]
+ #[repr(C)]
+ pub struct sockaddr_ll {
+ pub sll_family: u16,
+ pub sll_protocol: u16,
+ pub sll_ifindex: u32,
+ pub sll_hatype: u16,
+ pub sll_pkttype: u8,
+ pub sll_halen: u8,
+ pub sll_addr: [u8; 8],
+ }
+
+ #[cfg(target_os = "linux")]
+ pub mod linux {
+ pub use super::*;
+
+ #[cfg(target_arch = "x86")]
+ pub const SYS_getrandom: c_ulong = 355;
+ #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
+ pub const SYS_getrandom: c_ulong = 0x40000000 + 318;
+ #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
+ pub const SYS_getrandom: c_ulong = 318;
+ #[cfg(target_arch = "arm")]
+ pub const SYS_getrandom: c_ulong = 384;
+ #[cfg(target_arch = "aarch64")]
+ pub const SYS_getrandom: c_ulong = 278;
+ #[cfg(target_arch = "mips")]
+ pub const SYS_getrandom: c_ulong = 4000 + 353;
+ #[cfg(target_arch = "mips64")]
+ pub const SYS_getrandom: c_ulong = 5000 + 313;
+ #[cfg(any(
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "loongarch64"
+ ))]
+ pub const SYS_getrandom: c_ulong = 278;
+ #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
+ pub const SYS_getrandom: c_ulong = 359;
+ #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
+ pub const SYS_getrandom: c_ulong = 347;
+ #[cfg(target_arch = "s390x")]
+ pub const SYS_getrandom: c_ulong = 349;
+ #[cfg(target_arch = "m68k")]
+ pub const SYS_getrandom: c_ulong = 352;
+ #[cfg(not(any(
+ target_arch = "x86",
+ target_arch = "x86_64",
+ target_arch = "arm",
+ target_arch = "aarch64",
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "loongarch64",
+ target_arch = "powerpc",
+ target_arch = "powerpc64",
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ target_arch = "s390x",
+ target_arch = "m68k",
+ )))]
+ pub const SYS_getrandom: c_ulong = 0;
+
+ extern "C" {
+ pub fn syscall(num: c_ulong, ...) -> c_long;
+ }
+ }
+
+ #[cfg(ptp_helper_permissions = "setcap")]
+ pub mod setcaps {
+ use super::*;
+
+ pub type cap_t = *mut c_void;
+
+ #[link(name = "cap")]
+ extern "C" {
+ pub fn cap_clear(c: cap_t) -> c_int;
+ pub fn cap_get_proc() -> cap_t;
+ pub fn cap_set_proc(c: cap_t) -> c_int;
+ pub fn cap_free(c: cap_t) -> c_int;
+ }
+ }
+
+ #[cfg(ptp_helper_permissions = "setuid-root")]
+ pub mod setuid_root {
+ use super::*;
+
+ pub type uid_t = u32;
+ pub type gid_t = u32;
+
+ #[repr(C)]
+ pub struct passwd {
+ pub pw_name: *mut c_char,
+ pub pw_passwd: *mut c_char,
+ pub pw_uid: uid_t,
+ pub pw_gid: gid_t,
+ // More fields following here
+ truncated: c_void,
+ }
+
+ #[repr(C)]
+ pub struct group {
+ pub gr_name: *mut c_char,
+ pub gr_passwd: *mut c_char,
+ pub gr_gid: gid_t,
+ // More fields following here
+ truncated: c_void,
+ }
+
+ extern "C" {
+ #[cfg_attr(target_os = "netbsd", link_name = "__getpwnam50")]
+ pub fn getpwnam(name: *const c_char) -> *mut passwd;
+ pub fn getgrnam(name: *const c_char) -> *mut group;
+ pub fn setgid(gid: gid_t) -> c_int;
+ pub fn getgid() -> gid_t;
+ pub fn setuid(gid: gid_t) -> c_int;
+ }
+ }
+}
+
+#[cfg(windows)]
+#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
+pub mod windows {
+ use std::os::{
+ raw::*,
+ windows::raw::{HANDLE, SOCKET},
+ };
+
+ // The API definitions below are taken from the windows-sys crate version 0.45.0, the
+ // corresponding C headers, MSDN and related documentation.
+ //
+ // XXX: Once meson has cargo subproject support all of the below can be replaced with the windows-sys crate.
+ pub const STD_INPUT_HANDLE: i32 = -10;
+ pub const STD_OUTPUT_HANDLE: i32 = -11;
+
+ pub const FILE_TYPE_CHAR: u32 = 0x0002;
+ pub const FILE_TYPE_PIPE: u32 = 0x0003;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ pub fn GetStdHandle(nstdhandle: i32) -> HANDLE;
+
+ pub fn ReadFile(
+ hfile: HANDLE,
+ lpbuffer: *mut u8,
+ nnumberofbytestoread: u32,
+ lpnumberofbytesread: *mut u32,
+ lpoverlapped: *mut c_void,
+ ) -> i32;
+
+ pub fn WriteFile(
+ hfile: HANDLE,
+ lpbuffer: *const u8,
+ nnumberofbytestowrite: u32,
+ lpnumberofbyteswritten: *mut u32,
+ lpoverlapped: *mut c_void,
+ ) -> i32;
+
+ pub fn WaitForMultipleObjects(
+ ncount: u32,
+ lphandles: *const HANDLE,
+ bwaitall: i32,
+ dwmilliseconds: u32,
+ ) -> u32;
+
+ pub fn SetConsoleMode(hconsolehandle: HANDLE, dwmode: u32) -> i32;
+ pub fn FlushConsoleInputBuffer(hconsolehandle: HANDLE) -> i32;
+
+ pub fn CreateEventA(
+ lpeventattributes: *const c_void,
+ bmanualreset: i32,
+ binitialstate: i32,
+ lpname: *const u8,
+ ) -> HANDLE;
+ pub fn SetEvent(hevent: HANDLE) -> i32;
+ pub fn ResetEvent(hevent: HANDLE) -> i32;
+ pub fn CloseHandle(hobject: HANDLE) -> i32;
+
+ pub fn GetFileType(hfile: HANDLE) -> u32;
+
+ pub fn GetProcessHeap() -> isize;
+ pub fn HeapAlloc(hheap: isize, dwflags: u32, dwbytes: usize) -> *mut c_void;
+ pub fn HeapFree(hheap: isize, dwflags: u32, lpmem: *mut c_void) -> i32;
+ pub fn HeapReAlloc(
+ hheap: isize,
+ dwflags: u32,
+ lpmem: *mut c_void,
+ dwbytes: usize,
+ ) -> *mut c_void;
+ }
+
+ pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
+
+ #[link(name = "bcrypt")]
+ extern "system" {
+ pub fn BCryptGenRandom(
+ hAlgorithm: *mut c_void,
+ pBuffer: *mut u8,
+ cbBuffer: u32,
+ dwFlags: u32,
+ ) -> u32;
+ }
+
+ pub const FD_READ: u32 = 1;
+ pub const FD_READ_BIT: usize = 0;
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct WSANETWORKEVENTS {
+ pub lnetworkevents: u32,
+ pub ierrorcode: [i32; 10],
+ }
+
+ pub const IPPROTO_IP: u32 = 0u32;
+ pub const IP_ADD_MEMBERSHIP: u32 = 12u32;
+
+ pub const SOL_SOCKET: u32 = 65535;
+ pub const SO_REUSEADDR: u32 = 4;
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IN_ADDR_0_0 {
+ pub s_b1: u8,
+ pub s_b2: u8,
+ pub s_b3: u8,
+ pub s_b4: u8,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IN_ADDR_0_1 {
+ pub s_w1: u16,
+ pub s_w2: u16,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub union IN_ADDR_0 {
+ pub S_un_b: IN_ADDR_0_0,
+ pub S_un_w: IN_ADDR_0_1,
+ pub S_addr: u32,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IN_ADDR {
+ pub S_un: IN_ADDR_0,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IP_MREQ {
+ pub imr_multiaddr: IN_ADDR,
+ pub imr_address: IN_ADDR,
+ }
+
+ #[link(name = "ws2_32")]
+ extern "system" {
+ pub fn WSAEventSelect(s: SOCKET, heventobject: HANDLE, lnetworkevents: u32) -> i32;
+ pub fn WSAEnumNetworkEvents(
+ s: SOCKET,
+ heventobject: HANDLE,
+ lpnetworkevents: *mut WSANETWORKEVENTS,
+ ) -> i32;
+ pub fn WSACreateEvent() -> HANDLE;
+ pub fn WSACloseEvent(hevent: HANDLE) -> i32;
+ pub fn WSAGetLastError() -> i32;
+
+ pub fn setsockopt(
+ socket: SOCKET,
+ level: i32,
+ name: i32,
+ value: *const c_void,
+ option_len: i32,
+ ) -> i32;
+ }
+
+ pub const AF_INET: u32 = 2;
+
+ pub const GAA_FLAG_SKIP_ANYCAST: u32 = 0x0002;
+ pub const GAA_FLAG_SKIP_MULTICAST: u32 = 0x0004;
+ pub const GAA_FLAG_SKIP_DNS_SERVER: u32 = 0x0008;
+
+ pub const ADAPTER_FLAG_RECEIVE_ONLY: u32 = 0x08;
+ pub const ADAPTER_FLAG_NO_MULTICAST: u32 = 0x10;
+ pub const ADAPTER_FLAG_IPV4_ENABLED: u32 = 0x80;
+
+ pub const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24;
+
+ pub const IF_OPER_STATUS_UP: u32 = 1;
+
+ pub const ERROR_NOT_ENOUGH_MEMORY: u32 = 8u32;
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IP_ADAPTER_ADDRESSES_LH_0_0 {
+ pub length: u32,
+ pub ifindex: u32,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub union IP_ADAPTER_ADDRESSES_LH_0 {
+ pub alignment: u64,
+ pub anonymous: IP_ADAPTER_ADDRESSES_LH_0_0,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct IP_ADAPTER_UNICAST_ADDRESS_LH_0_0 {
+ pub length: u32,
+ pub ifindex: u32,
+ }
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub union IP_ADAPTER_UNICAST_ADDRESS_LH_0 {
+ pub alignment: u64,
+ pub anonymous: IP_ADAPTER_UNICAST_ADDRESS_LH_0_0,
+ }
+
+ #[repr(C)]
+ pub struct SOCKADDR {
+ pub sa_family: u16,
+ pub sin_port: u16,
+ pub in_addr: IN_ADDR,
+ }
+
+ #[repr(C)]
+ pub struct SOCKET_ADDRESS {
+ pub lpsocketaddr: *mut SOCKADDR,
+ pub isocketaddrlength: i32,
+ }
+
+ #[repr(C)]
+ pub struct IP_ADAPTER_UNICAST_ADDRESS_LH {
+ pub anonymous: IP_ADAPTER_UNICAST_ADDRESS_LH_0,
+ pub next: *mut IP_ADAPTER_UNICAST_ADDRESS_LH,
+ pub address: SOCKET_ADDRESS,
+ // More fields following here
+ truncated: c_void,
+ }
+
+ #[repr(C)]
+ pub struct IP_ADAPTER_ADDRESSES_LH {
+ pub anonymous: IP_ADAPTER_ADDRESSES_LH_0,
+ pub next: *mut IP_ADAPTER_ADDRESSES_LH,
+ pub adaptername: *const u8,
+ pub firstunicastaddress: *mut IP_ADAPTER_UNICAST_ADDRESS_LH,
+ pub firstanycastaddress: *mut c_void,
+ pub firstmulticastaddress: *mut c_void,
+ pub firstdnsserveraddress: *mut c_void,
+ pub dnssuffix: *const u16,
+ pub description: *const u16,
+ pub friendlyname: *const u16,
+ pub physicaladdress: [u8; 8],
+ pub physicaladdresslength: u32,
+ pub flags: u32,
+ pub mtu: u32,
+ pub iftype: u32,
+ pub operstatus: u32,
+ // More fields following here
+ truncated: c_void,
+ }
+
+ #[link(name = "iphlpapi")]
+ extern "system" {
+ pub fn GetAdaptersAddresses(
+ family: u32,
+ flags: u32,
+ reserved: *mut c_void,
+ adapteraddresses: *mut IP_ADAPTER_ADDRESSES_LH,
+ sizepointer: *mut u32,
+ ) -> u32;
+ }
+}
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs
new file mode 100644
index 0000000000..7b0e064d51
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs
@@ -0,0 +1,769 @@
+// 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
+
+#[cfg(unix)]
+mod imp {
+ use std::{
+ io::{self, Read, Write},
+ net::UdpSocket,
+ os::unix::io::{AsRawFd, RawFd},
+ };
+
+ use crate::{bail, error::Error, ffi::unix::*};
+
+ /// Inputs and outputs, and allowing to poll the inputs for available data.
+ ///
+ /// This carries the event/general UDP socket and stdin/stdout.
+ pub struct Poll {
+ event_socket: UdpSocket,
+ general_socket: UdpSocket,
+ stdin: Stdin,
+ stdout: Stdout,
+ }
+
+ /// Result of polling the inputs of the `Poll`.
+ ///
+ /// Any input that has data available for reading will be set to `true`, potentially multiple
+ /// at once.
+ ///
+ /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so
+ /// special care has to be taken to only read as much as is available.
+ pub struct PollResult {
+ pub event_socket: bool,
+ pub general_socket: bool,
+ pub stdin: bool,
+ }
+
+ impl Poll {
+ /// Name of the input based on the `struct pollfd` index.
+ fn fd_name(idx: usize) -> &'static str {
+ match idx {
+ 0 => "event socket",
+ 1 => "general socket",
+ 2 => "stdin",
+ _ => unreachable!(),
+ }
+ }
+
+ /// Create a new `Poll` instance from the two sockets.
+ pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result<Self, Error> {
+ let stdin = Stdin::acquire();
+ let stdout = Stdout::acquire();
+
+ Ok(Self {
+ event_socket,
+ general_socket,
+ stdin,
+ stdout,
+ })
+ }
+
+ /// Mutable reference to the event socket.
+ pub fn event_socket(&mut self) -> &mut UdpSocket {
+ &mut self.event_socket
+ }
+
+ /// Mutable reference to the general socket.
+ pub fn general_socket(&mut self) -> &mut UdpSocket {
+ &mut self.general_socket
+ }
+
+ /// Mutable reference to stdin for reading.
+ pub fn stdin(&mut self) -> &mut Stdin {
+ &mut self.stdin
+ }
+
+ /// Mutable reference to stdout for writing.
+ pub fn stdout(&mut self) -> &mut Stdout {
+ &mut self.stdout
+ }
+
+ /// Poll the event socket, general socket and stdin for available data to read.
+ ///
+ /// This blocks until at least one input has data available.
+ pub fn poll(&mut self) -> Result<PollResult, Error> {
+ let mut pollfd = [
+ pollfd {
+ fd: self.event_socket.as_raw_fd(),
+ events: POLLIN,
+ revents: 0,
+ },
+ pollfd {
+ fd: self.general_socket.as_raw_fd(),
+ events: POLLIN,
+ revents: 0,
+ },
+ pollfd {
+ fd: self.stdin.0,
+ events: POLLIN,
+ revents: 0,
+ },
+ ];
+
+ // SAFETY: Polls the given pollfds above and requires a valid number to be passed.
+ // A negative timeout means that it will wait until at least one of the pollfds is
+ // ready.
+ //
+ // Will return -1 on error, otherwise the number of ready pollfds. This can never be
+ // zero as a non-empty set of pollfds is passed.
+ //
+ // On EINTR polling should be retried.
+ unsafe {
+ loop {
+ let res = poll(pollfd[..].as_mut_ptr(), pollfd.len() as _, -1);
+ if res == -1 {
+ let err = std::io::Error::last_os_error();
+ if err.kind() == std::io::ErrorKind::Interrupted {
+ continue;
+ }
+ bail!(source: err, "Failed polling");
+ }
+ assert_ne!(res, 0);
+ break;
+ }
+ }
+
+ // Check for errors or hangup first
+ for (idx, pfd) in pollfd.iter().enumerate() {
+ if pfd.revents & (POLLERR | POLLNVAL) != 0 {
+ bail!("Poll error on {}", Self::fd_name(idx));
+ }
+
+ if pfd.revents & POLLHUP != 0 {
+ bail!("Hang up during polling on {}", Self::fd_name(idx));
+ }
+ }
+
+ Ok(PollResult {
+ event_socket: pollfd[0].revents & POLLIN != 0,
+ general_socket: pollfd[1].revents & POLLIN != 0,
+ stdin: pollfd[2].revents & POLLIN != 0,
+ })
+ }
+ }
+
+ /// Raw, unbuffered handle to `stdin`.
+ ///
+ /// This implements the `Read` trait for reading.
+ pub struct Stdin(RawFd);
+
+ impl Stdin {
+ fn acquire() -> Self {
+ Stdin(STDIN_FILENO)
+ }
+ }
+
+ impl Read for Stdin {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ // SAFETY: read() requires a valid fd and a mutable buffer with the given size.
+ // The fd is valid by construction as is the buffer.
+ //
+ // read() will return the number of bytes read or a negative value on errors.
+ let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) };
+
+ if res < 0 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(res as usize)
+ }
+ }
+ }
+
+ /// Raw, unbuffered handle to `stdout`.
+ ///
+ /// This implements the `Write` trait for writing.
+ pub struct Stdout(RawFd);
+
+ impl Stdout {
+ fn acquire() -> Self {
+ Stdout(STDOUT_FILENO)
+ }
+ }
+
+ impl Write for Stdout {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // SAFETY: write() requires a valid fd and a mutable buffer with the given size.
+ // The fd is valid by construction as is the buffer.
+ //
+ // write() will return the number of bytes written or a negative value on errors.
+ let res = unsafe { write(self.0, buf.as_ptr(), buf.len()) };
+
+ if res == -1 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(res as usize)
+ }
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+}
+
+#[cfg(windows)]
+mod imp {
+ use std::{
+ cmp,
+ io::{self, Read, Write},
+ mem,
+ net::UdpSocket,
+ os::windows::{io::AsRawSocket, raw::HANDLE},
+ ptr,
+ sync::{Arc, Condvar, Mutex},
+ thread,
+ };
+
+ use crate::{
+ bail,
+ error::{Context, Error},
+ ffi::windows::*,
+ };
+
+ /// Inputs and outputs, and allowing to poll the inputs for available data.
+ ///
+ /// This carries the event/general UDP socket and stdin/stdout.
+ pub struct Poll {
+ event_socket: UdpSocket,
+ event_socket_event: EventHandle,
+ general_socket: UdpSocket,
+ general_socket_event: EventHandle,
+ stdin: Stdin,
+ stdout: Stdout,
+ }
+
+ /// Helper struct for a WSA event.
+ struct EventHandle(HANDLE);
+
+ impl EventHandle {
+ fn new() -> io::Result<Self> {
+ // SAFETY: WSACreateEvent() returns 0 on error or otherwise a valid WSA event
+ // that has to be closed again later.
+ unsafe {
+ let event = WSACreateEvent();
+ if event.is_null() || event as isize == -1 {
+ Err(io::Error::from_raw_os_error(WSAGetLastError()))
+ } else {
+ Ok(EventHandle(event))
+ }
+ }
+ }
+ }
+
+ impl Drop for EventHandle {
+ fn drop(&mut self) {
+ // SAFETY: The event is valid by construction and dropped at most once, so can be
+ // safely closed here..
+ //
+ // The return value is intentionally ignored as nothing else can be done
+ // on errors anyway.
+ unsafe {
+ let _ = WSACloseEvent(self.0);
+ }
+ }
+ }
+
+ /// Result of polling the inputs of the `Poll`.
+ ///
+ /// Any input that has data available for reading will be set to `true`, potentially multiple
+ /// at once.
+ ///
+ /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so
+ /// special care has to be taken to only read as much as is available.
+ pub struct PollResult {
+ pub event_socket: bool,
+ pub general_socket: bool,
+ pub stdin: bool,
+ }
+
+ impl Poll {
+ /// Create a new `Poll` instance from the two sockets.
+ pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result<Self, Error> {
+ let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?;
+ let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?;
+
+ // Create event objects for the readability of the sockets.
+ let event_socket_event = EventHandle::new().context("Failed creating WSA event")?;
+ let general_socket_event = EventHandle::new().context("Failed creating WSA event")?;
+
+ // SAFETY: WSAEventSelect() requires a valid socket and WSA event, which are both
+ // passed here, and the bitflag of events that should be selected for.
+ //
+ // On error a non-zero value is returned.
+ unsafe {
+ if WSAEventSelect(event_socket.as_raw_socket(), event_socket_event.0, FD_READ) != 0
+ {
+ bail!(
+ source: io::Error::from_raw_os_error(WSAGetLastError()),
+ "Failed selecting for read events on event socket"
+ );
+ }
+
+ if WSAEventSelect(
+ general_socket.as_raw_socket(),
+ general_socket_event.0,
+ FD_READ,
+ ) != 0
+ {
+ bail!(
+ source: io::Error::from_raw_os_error(WSAGetLastError()),
+ "Failed selecting for read events on general socket"
+ );
+ }
+ }
+
+ Ok(Self {
+ event_socket,
+ event_socket_event,
+ general_socket,
+ general_socket_event,
+ stdin,
+ stdout,
+ })
+ }
+
+ /// Mutable reference to the event socket.
+ pub fn event_socket(&mut self) -> &mut UdpSocket {
+ &mut self.event_socket
+ }
+
+ /// Mutable reference to the general socket.
+ pub fn general_socket(&mut self) -> &mut UdpSocket {
+ &mut self.general_socket
+ }
+
+ /// Mutable reference to stdin for reading.
+ pub fn stdin(&mut self) -> &mut Stdin {
+ &mut self.stdin
+ }
+
+ /// Mutable reference to stdout for writing.
+ pub fn stdout(&mut self) -> &mut Stdout {
+ &mut self.stdout
+ }
+
+ /// Poll the event socket, general socket and stdin for available data to read.
+ ///
+ /// This blocks until at least one input has data available.
+ pub fn poll(&mut self) -> Result<PollResult, Error> {
+ let handles = [
+ self.event_socket_event.0,
+ self.general_socket_event.0,
+ // If stdin is a pipe then we use the signalling event, otherwise stdin itself.
+ if let Some(ref thread_state) = self.stdin.thread_state {
+ thread_state.event
+ } else {
+ self.stdin.handle
+ },
+ ];
+
+ // If stdin is a pipe and currently no data is pending on it then signal
+ // the reading thread to try reading one byte and blocking for that long.
+ if let Some(ref mut thread_state) = self.stdin.thread_state {
+ let mut guard = thread_state.buffer.lock().unwrap();
+ if !guard.buffer_filled && !guard.fill_buffer {
+ guard.fill_buffer = true;
+ // SAFETY: The thread's event is valid by construction until the thread
+ // is stopped, and can be reset at any time.
+ unsafe {
+ ResetEvent(thread_state.event);
+ }
+ thread_state.buffer_cond.notify_one();
+ }
+ }
+
+ // SAFETY: Wait for the socket/stdin objects to become ready. This requires a valid
+ // array of valid handles and the corresponding length, whether it should wait for all
+ // handles (no), and a timeout (infinity).
+ //
+ // On error u32::MAX is returned, otherwise an index into the array of handles is
+ // returned for the handle that became ready.
+ let res = unsafe {
+ let res =
+ WaitForMultipleObjects(handles.len() as _, handles[..].as_ptr(), 0, u32::MAX);
+ if res == u32::MAX {
+ bail!(
+ source: io::Error::from_raw_os_error(WSAGetLastError()),
+ "Failed waiting for events"
+ );
+ }
+
+ assert!(
+ (0..=2).contains(&res),
+ "Unexpected WaitForMultipleObjects() return value {}",
+ res,
+ );
+
+ res
+ };
+
+ // For the sockets, enumerate the events that woke up the waiting, collect any errors
+ // and reset the event objects.
+ if (0..=1).contains(&res) {
+ let (socket, event) = if res == 0 {
+ (&self.event_socket, &self.event_socket_event)
+ } else {
+ (&self.general_socket, &self.general_socket_event)
+ };
+
+ // SAFETY: Requires a valid socket and event, which is given by construction here.
+ // The passed in memory for the network events will be filled if no error happens,
+ // and the function returns a non-zero value if an error has happened.
+ let networkevents = unsafe {
+ let mut networkevents = mem::MaybeUninit::uninit();
+ if WSAEnumNetworkEvents(
+ socket.as_raw_socket(),
+ event.0,
+ networkevents.as_mut_ptr(),
+ ) != 0
+ {
+ bail!(
+ source: io::Error::from_raw_os_error(WSAGetLastError()),
+ "Failed enumerating network events on {} socket",
+ if res == 0 { "event" } else { "general" },
+ );
+ }
+
+ networkevents.assume_init()
+ };
+
+ if networkevents.ierrorcode[FD_READ_BIT] != 0 {
+ bail!(
+ source: io::Error::from_raw_os_error(networkevents.ierrorcode[FD_READ_BIT]),
+ "Error on {} socket while waiting for events",
+ if res == 0 { "event" } else { "general" },
+ );
+ }
+ assert!(networkevents.lnetworkevents & FD_READ != 0);
+ }
+
+ Ok(PollResult {
+ event_socket: res == 0,
+ general_socket: res == 1,
+ stdin: res == 2,
+ })
+ }
+ }
+
+ /// Raw, unbuffered handle to `stdin`.
+ ///
+ /// This implements the `Read` trait for reading.
+ pub struct Stdin {
+ handle: HANDLE,
+ thread_state: Option<Arc<StdinThreadState>>,
+ join_handle: Option<thread::JoinHandle<()>>,
+ }
+
+ struct StdinThreadState {
+ buffer: Mutex<StdinBuffer>,
+ buffer_cond: Condvar,
+ event: HANDLE,
+ handle: HANDLE,
+ }
+
+ unsafe impl Send for StdinThreadState {}
+ unsafe impl Sync for StdinThreadState {}
+
+ struct StdinBuffer {
+ buffer: [u8; 1],
+ error: Option<io::Error>,
+ buffer_filled: bool,
+ fill_buffer: bool,
+ shutdown: bool,
+ }
+
+ impl Drop for Stdin {
+ fn drop(&mut self) {
+ // If stdin was a pipe and a thread was started to check for read-readiness
+ // then stop this thread now and release its resources.
+ if let Some(ref thread_state) = self.thread_state {
+ let mut guard = thread_state.buffer.lock().unwrap();
+ guard.shutdown = true;
+ thread_state.buffer_cond.notify_one();
+ drop(guard);
+ let _ = self.join_handle.take().unwrap().join();
+
+ // SAFETY: The thread is stopped now so the event is not used by anything else
+ // anymore and can safely be closed now.
+ //
+ // The return value is explicitly ignored because nothing can be done on error
+ // anyway.
+ unsafe {
+ let _ = CloseHandle(thread_state.event);
+ }
+ }
+ }
+ }
+
+ impl Stdin {
+ fn acquire() -> Result<Self, Error> {
+ // SAFETY: GetStdHandle returns a borrowed handle, or 0 if none is set or -1 if an
+ // error has happened.
+ let handle = unsafe {
+ let handle = GetStdHandle(STD_INPUT_HANDLE);
+ if handle.is_null() {
+ bail!("No stdin handle set");
+ } else if handle as isize == -1 {
+ bail!(source: io::Error::last_os_error(), "Can't get stdin handle");
+ }
+
+ handle
+ };
+ // SAFETY: GetFileType() is safe to call on any valid handle.
+ let type_ = unsafe { GetFileType(handle) };
+
+ if type_ == FILE_TYPE_CHAR {
+ // Set the console to raw mode and flush any pending input.
+ //
+ // SAFETY: Calling this on non-console handles will cause an error but otherwise
+ // have no negative effects. We can safely change the console mode here as nothing
+ // else is accessing the console.
+ unsafe {
+ let _ = SetConsoleMode(handle, 0);
+ let _ = FlushConsoleInputBuffer(handle);
+ }
+
+ Ok(Stdin {
+ handle,
+ thread_state: None,
+ join_handle: None,
+ })
+ } else if type_ == FILE_TYPE_PIPE {
+ // XXX: Because g_spawn() creates the overridden pipes with _pipe(), they're
+ // 1. Full duplex, so WaitForMultipleObjects() always considers them ready as you can write
+ // 2. Not overlapped so only synchronous IO can be used
+ // To work around this we're creating a thread here that just reads synchronously
+ // from stdin to signal ready-ness.
+
+ // SAFETY: Creating an event handle with all-zero parameters is valid and on error
+ // a NULL handle will be returned. Otherwise a valid event handle is returned that
+ // needs to be closed again later, which happens as part of the StdinThreadState
+ // Drop implementation.
+ let event = unsafe {
+ let event = CreateEventA(ptr::null(), 0, 0, ptr::null());
+ if event.is_null() {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed creating event handle"
+ );
+ }
+
+ event
+ };
+ let thread_state = Arc::new(StdinThreadState {
+ buffer: Mutex::new(StdinBuffer {
+ buffer: [0],
+ error: None,
+ buffer_filled: false,
+ fill_buffer: true,
+ shutdown: false,
+ }),
+ buffer_cond: Condvar::new(),
+ event,
+ handle,
+ });
+
+ let join_handle = thread::spawn({
+ let thread_state = thread_state.clone();
+ move || Self::stdin_readiness_thread(&thread_state)
+ });
+
+ Ok(Stdin {
+ handle,
+ thread_state: Some(thread_state),
+ join_handle: Some(join_handle),
+ })
+ } else {
+ bail!("unhandled stdin handle type {:x}", type_);
+ }
+ }
+
+ /// Thread function to signal readiness of stdin.
+ ///
+ /// This thread tries to read a single byte and buffers it, then signals an event
+ /// object and waits for reading to finish and a new call to `poll()` to
+ /// start trying to read a single byte again.
+ fn stdin_readiness_thread(thread_state: &StdinThreadState) {
+ loop {
+ let mut buffer = [0u8];
+ // SAFETY: Reads one byte from the handle synchronously. Nothing else is currently
+ // reading from the handle as this thread is waiting below on the condition
+ // variable as long as a single byte was read already, and only wakes up again if a
+ // full packet was read from stdin and the other thread is waiting on the event
+ // handle again.
+ let res = unsafe {
+ let mut bytes_read = mem::MaybeUninit::uninit();
+ let res = ReadFile(
+ thread_state.handle,
+ buffer[..].as_mut_ptr(),
+ buffer.len() as u32,
+ bytes_read.as_mut_ptr(),
+ ptr::null_mut(),
+ );
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(bytes_read.assume_init())
+ }
+ };
+
+ let mut guard = thread_state.buffer.lock().unwrap();
+ assert!(!guard.buffer_filled);
+ assert!(guard.fill_buffer);
+ if guard.shutdown {
+ break;
+ }
+ guard.buffer_filled = true;
+ guard.fill_buffer = false;
+ match res {
+ Err(err) => {
+ guard.error = Some(err);
+ }
+ Ok(bytes_read) => {
+ guard.buffer[0] = buffer[0];
+ assert_eq!(bytes_read, 1);
+ }
+ }
+
+ // SAFETY: Signalling an event is valid from any thread at any time and the event
+ // handle is valid by construction.
+ unsafe {
+ SetEvent(thread_state.event);
+ }
+ while !guard.shutdown && !guard.fill_buffer {
+ guard = thread_state.buffer_cond.wait(guard).unwrap();
+ }
+ if guard.shutdown {
+ break;
+ }
+ }
+ }
+ }
+
+ impl Read for Stdin {
+ fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
+ if buf.is_empty() {
+ return Ok(0);
+ }
+
+ // If a read byte is pending from the readiness signalling thread then
+ // read that first here before reading any remaining data.
+ let mut already_read = 0;
+ if let Some(ref mut thread_state) = self.thread_state {
+ let mut guard = thread_state.buffer.lock().unwrap();
+ assert!(!guard.fill_buffer);
+ if guard.buffer_filled {
+ guard.buffer_filled = false;
+ if let Some(err) = guard.error.take() {
+ return Err(err);
+ }
+ buf[0] = guard.buffer[0];
+ if buf.len() == 1 {
+ return Ok(1);
+ }
+ buf = &mut buf[1..];
+ already_read = 1;
+ }
+ }
+
+ // SAFETY: Reads the given number of bytes into the buffer from the stdin handle.
+ // The other thread is not currently reading as checked above, and would only be
+ // triggered to read again by this thread once poll() is called.
+ unsafe {
+ let mut lpnumberofbytesread = mem::MaybeUninit::uninit();
+ let res = ReadFile(
+ self.handle,
+ buf.as_mut_ptr(),
+ cmp::min(buf.len() as u32, u32::MAX) as u32,
+ lpnumberofbytesread.as_mut_ptr(),
+ ptr::null_mut(),
+ );
+
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(lpnumberofbytesread.assume_init() as usize + already_read)
+ }
+ }
+ }
+ }
+
+ /// Raw, unbuffered handle to `stdout`.
+ ///
+ /// This implements the `Write` trait for writing.
+ pub struct Stdout(HANDLE);
+
+ impl Stdout {
+ fn acquire() -> Result<Self, Error> {
+ // SAFETY: GetStdHandle returns a borrowed handle, or 0 if none is set or -1 if an
+ // error has happened.
+ let handle = unsafe {
+ let handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ if handle.is_null() {
+ bail!("No stdout handle set");
+ } else if handle as isize == -1 {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Can't get stdout handle"
+ );
+ }
+
+ handle
+ };
+ // SAFETY: GetFileType() is safe to call on any valid handle.
+ let type_ = unsafe { GetFileType(handle) };
+
+ if type_ == FILE_TYPE_CHAR {
+ // Set the console to raw mode.
+ //
+ // SAFETY: Calling this on non-console handles will cause an error but otherwise
+ // have no negative effects. We can safely change the console mode here as nothing
+ // else is accessing the console.
+ unsafe {
+ let _ = SetConsoleMode(handle, 0);
+ }
+ } else if type_ != FILE_TYPE_PIPE {
+ bail!("Unsupported stdout handle type {:x}", type_);
+ }
+
+ Ok(Stdout(handle))
+ }
+ }
+
+ impl Write for Stdout {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // SAFETY: Writes the given number of bytes to stdout or at most u32::MAX. On error
+ // zero is returned, otherwise the number of bytes written is set accordingly and
+ // returned.
+ unsafe {
+ let mut lpnumberofbyteswritten = mem::MaybeUninit::uninit();
+ let res = WriteFile(
+ self.0,
+ buf.as_ptr(),
+ cmp::min(buf.len() as u32, u32::MAX) as u32,
+ lpnumberofbyteswritten.as_mut_ptr(),
+ ptr::null_mut(),
+ );
+
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(lpnumberofbyteswritten.assume_init() as usize)
+ }
+ }
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+}
+
+pub use self::imp::{Poll, PollResult, Stdin, Stdout};
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs
new file mode 100644
index 0000000000..dc3173085e
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs
@@ -0,0 +1,232 @@
+// 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 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;
+
+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;
+
+/// Create a new `UdpSocket` for the given port and configure it for PTP.
+fn create_socket(port: u16) -> Result<UdpSocket, Error> {
+ 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::<Ipv4Addr>() {
+ 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")?;
+
+ 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; 4 + 8];
+ clock_id_data[0..2].copy_from_slice(&8u16.to_le_bytes());
+ clock_id_data[2] = 2;
+ clock_id_data[3] = 0;
+ clock_id_data[4..].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; 1500];
+ let mut stdinout_buffer = [0u8; 1504];
+
+ 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 res = match idx {
+ 0 => poll.event_socket().recv(&mut socket_buffer),
+ 1 => 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_ne_bytes());
+ stdinout_buffer[2] = idx as u8;
+ stdinout_buffer[3] = 0;
+ stdinout_buffer[4..][..read].copy_from_slice(&socket_buffer[..read]);
+
+ poll.stdout()
+ .write_all(&stdinout_buffer[..(read + 4)])
+ .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..4])
+ .context("Failed reading packet header from stdin")?;
+
+ let size = u16::from_ne_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_ {
+ 0 => poll
+ .event_socket()
+ .send_to(buf, (PTP_MULTICAST_ADDR, PTP_EVENT_PORT)),
+ 1 => 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" }
+ )
+ })?;
+ }
+ }
+}
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build
new file mode 100644
index 0000000000..401b3e1fe3
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build
@@ -0,0 +1,91 @@
+# Check PTP support
+have_rust = add_languages('rust', native : false, required : false)
+have_ptp = true
+if not have_rust
+ have_ptp = false
+ message('PTP not supported without Rust compiler')
+endif
+
+if not ['linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'darwin', 'sunos', 'solaris', 'illumos', 'windows'].contains(host_system)
+ have_ptp = false
+ message('PTP not supported on this OS')
+endif
+
+if have_ptp
+ rustc = meson.get_compiler('rust')
+ cdata.set('HAVE_PTP', 1, description : 'PTP support available')
+
+ ptp_helper_conf_data = configuration_data()
+ rust_args = []
+
+ 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 != ''
+ ptp_helper_conf_data.set('PTP_HELPER_SETUID_USER', 'Some("@0@")'.format(ptp_helper_setuid_user))
+ else
+ ptp_helper_conf_data.set('PTP_HELPER_SETUID_USER', 'None')
+ endif
+ ptp_helper_setuid_group = get_option('ptp-helper-setuid-group')
+ if ptp_helper_setuid_group != ''
+ ptp_helper_conf_data.set('PTP_HELPER_SETUID_GROUP', 'Some("@0@")'.format(ptp_helper_setuid_group))
+ else
+ ptp_helper_conf_data.set('PTP_HELPER_SETUID_GROUP', 'None')
+ 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'
+ elif host_system == 'windows'
+ with_ptp_helper_permissions = 'none'
+ 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'
+ rust_args += ['--cfg', 'ptp_helper_permissions="none"']
+ # nothing to do
+ elif with_ptp_helper_permissions == 'setuid-root'
+ rust_args += ['--cfg', 'ptp_helper_permissions="setuid-root"']
+ 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
+ rust_args += ['--cfg', 'ptp_helper_permissions="setcap"']
+ else
+ error('Unexpected ptp helper permissions value: ' + with_ptp_helper_permissions)
+ endif
+
+ conf_lib_rs = configure_file(input : 'conf_lib.rs.in',
+ output : 'conf_lib.rs',
+ configuration: ptp_helper_conf_data)
+
+ conf = static_library('gst_ptp_helper_conf', conf_lib_rs,
+ override_options : ['rust_std=2018'],
+ rust_args : ['-Cpanic=abort'],
+ rust_crate_type : 'rlib')
+
+ exe = executable('gst-ptp-helper', 'main.rs',
+ override_options : ['rust_std=2018'],
+ rust_args : ['-Cpanic=abort', rust_args],
+ dependencies : [cap_dep],
+ link_with : conf,
+ install_dir : helpers_install_dir,
+ install : true)
+
+ if host_system != 'windows'
+ meson.add_install_script('ptp_helper_post_install.sh',
+ helpers_install_dir, with_ptp_helper_permissions,
+ setcap_prog.found() ? setcap_prog.full_path() : '')
+ endif
+
+ meson.add_devenv({'GST_PTP_HELPER': exe.full_path()})
+endif
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs
new file mode 100644
index 0000000000..e3b0bbddeb
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs
@@ -0,0 +1,667 @@
+// 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
+
+use std::net::Ipv4Addr;
+
+use crate::{bail, error::Error};
+
+#[derive(Debug)]
+/// Network interface information.
+pub struct InterfaceInfo {
+ /// Name of the interface
+ pub name: String,
+ /// Other name of the interface, if any
+ pub other_name: Option<String>,
+ /// Interface index
+ pub index: usize,
+ /// Unicast IPv4 address of the interface
+ pub ip_addr: Ipv4Addr,
+ /// Physical MAC address of the interface, if any
+ pub hw_addr: Option<[u8; 6]>,
+}
+
+#[cfg(unix)]
+mod imp {
+ use super::*;
+
+ use std::{ffi::CStr, io, marker, mem, net::UdpSocket, os::unix::io::AsRawFd, ptr};
+
+ use crate::{error::Context, ffi::unix::*};
+
+ /// Returns information for all non-loopback, multicast-capable network interfaces.
+ pub fn query_interfaces() -> Result<Vec<InterfaceInfo>, Error> {
+ struct Ifaddrs(*mut ifaddrs);
+
+ impl Ifaddrs {
+ fn new() -> io::Result<Self> {
+ loop {
+ // SAFETY: Requires passing valid storage for the returned ifaddrs pointer and
+ // returns -1 on errors. It might return NULL if there are no network interfaces.
+ //
+ // On error it might return EINTR in which case we should simply try again.
+ unsafe {
+ let mut ifaddrs = ptr::null_mut();
+ if getifaddrs(&mut ifaddrs) == -1 {
+ let err = io::Error::last_os_error();
+ if err.kind() == std::io::ErrorKind::Interrupted {
+ continue;
+ }
+
+ return Err(err);
+ } else {
+ return Ok(Self(ifaddrs));
+ }
+ }
+ }
+ }
+
+ fn iter(&self) -> IfaddrsIter {
+ IfaddrsIter {
+ ptr: ptr::NonNull::new(self.0),
+ phantom: marker::PhantomData,
+ }
+ }
+ }
+
+ impl Drop for Ifaddrs {
+ fn drop(&mut self) {
+ // SAFETY: The pointer is a valid ifaddrs pointer by construction and dropped only
+ // once, so freeing it here is OK. It might be NULL so check for that first.
+ unsafe {
+ if !self.0.is_null() {
+ freeifaddrs(self.0);
+ }
+ }
+ }
+ }
+
+ struct IfaddrsIter<'a> {
+ ptr: Option<ptr::NonNull<ifaddrs>>,
+ phantom: marker::PhantomData<&'a Ifaddrs>,
+ }
+
+ impl<'a> Iterator for IfaddrsIter<'a> {
+ type Item = &'a ifaddrs;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.ptr {
+ None => None,
+ Some(ptr) => {
+ // SAFETY: The pointer is a valid ifaddrs pointer by construction so
+ // creating a reference to it is OK.
+ let addr = unsafe { &*ptr.as_ptr() };
+ self.ptr = ptr::NonNull::new(addr.ifa_next);
+ Some(addr)
+ }
+ }
+ }
+ }
+
+ let ifaddrs = Ifaddrs::new().context("Failed getting interface addresses")?;
+
+ let mut if_infos = Vec::<InterfaceInfo>::new();
+
+ for ifaddr in ifaddrs.iter() {
+ // SAFETY: ifa_name points to a NUL-terminated interface name string that is valid as
+ // long as its struct is
+ let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) }.to_str().unwrap();
+
+ // Skip loopback interfaces, interfaces that are not up and interfaces that can't do
+ // multicast. These are all unusable for PTP purposes.
+ let flags = ifaddr.ifa_flags;
+ if (flags & IFF_LOOPBACK as u32 != 0)
+ || (flags & IFF_UP as u32 == 0)
+ || (flags & IFF_MULTICAST as u32 == 0)
+ {
+ continue;
+ }
+
+ // If the interface has no address then skip it. Only interfaces with IPv4 addresses
+ // are usable for PTP purposes.
+ if ifaddr.ifa_addr.is_null() {
+ continue;
+ }
+
+ // Get the interface index from its name. If it has none then we can't use it to join
+ // the PTP multicast group reliable for this interface.
+ //
+ // SAFETY: Must be called with a valid, NUL-terminated string which is provided by
+ // ifa_name and will return the interface index or zero on error.
+ //
+ // On error it can return EINTR in which case we should try again.
+ let index = loop {
+ let index = unsafe { if_nametoindex(ifaddr.ifa_name) } as usize;
+ if index == 0 {
+ let err = io::Error::last_os_error();
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue;
+ }
+ }
+ break index;
+ };
+ if index == 0 {
+ continue;
+ }
+
+ // Interfaces are listed multiple times here, once per address. We collect all IPv4 and
+ // MAC addresses for interfaces below.
+
+ // SAFETY: ifa_addr is a valid sockaddr pointer and was checked to be not NULL further
+ // above.
+ let sa_family = unsafe { (*ifaddr.ifa_addr).sa_family };
+
+ // If this interface has an IPv4 address then retrieve and store it here.
+ if sa_family == AF_INET as _ {
+ // SAFETY: If the address family is AF_INET then it is actually a valid sockaddr_in
+ // pointer and can be used as such.
+ let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_in) };
+ let ip_addr = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes());
+
+ if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) {
+ if if_info.ip_addr.is_broadcast() {
+ if_info.ip_addr = ip_addr;
+ }
+ } else {
+ if_infos.push(InterfaceInfo {
+ name: String::from(name),
+ other_name: None,
+ index,
+ ip_addr,
+ hw_addr: None,
+ });
+ }
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ if sa_family == AF_PACKET as _ {
+ // SAFETY: If the address family is AF_PACKET then it is actually a valid sockaddr_ll
+ // pointer and can be used as such.
+ let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_ll) };
+ if addr.sll_halen == 6 {
+ let mut hw_addr = [0u8; 6];
+ hw_addr.copy_from_slice(&addr.sll_addr[0..6]);
+ if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) {
+ if if_info.hw_addr.is_none() {
+ if_info.hw_addr = Some(hw_addr);
+ }
+ } else {
+ if_infos.push(InterfaceInfo {
+ name: String::from(name),
+ other_name: None,
+ index,
+ ip_addr: Ipv4Addr::BROADCAST,
+ hw_addr: Some(hw_addr),
+ });
+ }
+ }
+ }
+ }
+ #[cfg(not(target_os = "linux"))]
+ {
+ use std::slice;
+
+ if sa_family == AF_LINK as _ {
+ // SAFETY: If the address family is AF_LINK then it is actually a valid sockaddr_dl
+ // pointer and can be used as such.
+ let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_dl) };
+ if addr.sdl_nlen <= IF_NAMESIZE as u8 && addr.sdl_alen == 6 {
+ let mut hw_addr = [0u8; 6];
+ // SAFETY: There can be more than the given number of bytes stored and
+ // this happens regularly on macOS at least. It is required that the
+ // interface name is at most IF_NAMESIZE (checked just above).
+ unsafe {
+ let sdl_addr_ptr = addr.sdl_data.as_ptr() as *const u8;
+ let sdl_addr =
+ slice::from_raw_parts(sdl_addr_ptr.add(addr.sdl_nlen as usize), 6);
+ hw_addr.copy_from_slice(sdl_addr);
+ }
+
+ if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) {
+ if if_info.hw_addr.is_none() {
+ if_info.hw_addr = Some(hw_addr);
+ }
+ } else {
+ if_infos.push(InterfaceInfo {
+ name: String::from(name),
+ other_name: None,
+ index,
+ ip_addr: Ipv4Addr::BROADCAST,
+ hw_addr: Some(hw_addr),
+ });
+ }
+ }
+ }
+ }
+ }
+
+ if_infos.retain(|iface| !iface.ip_addr.is_broadcast());
+
+ Ok(if_infos)
+ }
+
+ // Join multicast address for a given interface.
+ pub fn join_multicast_v4(
+ socket: &UdpSocket,
+ addr: &Ipv4Addr,
+ iface: &InterfaceInfo,
+ ) -> Result<(), Error> {
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ {
+ let mreqn = ip_mreqn {
+ imr_multiaddr: in_addr {
+ s_addr: u32::from_ne_bytes(addr.octets()),
+ },
+ imr_address: in_addr {
+ s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()),
+ },
+ imr_ifindex: iface.index as _,
+ };
+
+ // SAFETY: Requires a valid ip_mreq or ip_mreqn struct to be passed together
+ // with its size for checking which of the two it is. On errors a negative
+ // integer is returned.
+ unsafe {
+ if setsockopt(
+ socket.as_raw_fd(),
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &mreqn as *const _ as *const _,
+ mem::size_of_val(&mreqn) as _,
+ ) < 0
+ {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed joining multicast group for interface {}",
+ iface.name,
+ );
+ }
+ }
+
+ Ok(())
+ }
+ #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+ {
+ use crate::error::Context;
+
+ socket
+ .join_multicast_v4(addr, &iface.ip_addr)
+ .with_context(|| {
+ format!(
+ "Failed joining multicast group for interface {} at address {}",
+ iface.name, iface.ip_addr
+ )
+ })?;
+
+ Ok(())
+ }
+ }
+
+ /// Allow multiple sockets to bind to the same address / port.
+ ///
+ /// This is best-effort and might not actually do anything.
+ pub fn set_reuse(socket: &UdpSocket) {
+ // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and
+ // enables the given feature on the socket.
+ //
+ // We explicitly ignore errors here. If it works, good, if it doesn't then not much
+ // lost other than the ability to run multiple processes at once.
+ unsafe {
+ let v = 1i32;
+ let _ = setsockopt(
+ socket.as_raw_fd(),
+ SOL_SOCKET,
+ SO_REUSEADDR,
+ &v as *const _ as *const _,
+ mem::size_of_val(&v) as u32,
+ );
+ }
+
+ #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
+ {
+ // SAFETY: SO_REUSEPORT takes an i32 value that can be 0/false or 1/true and
+ // enables the given feature on the socket.
+ //
+ // We explicitly ignore errors here. If it works, good, if it doesn't then not much
+ // lost other than the ability to run multiple processes at once.
+ unsafe {
+ let v = 1i32;
+ let _ = setsockopt(
+ socket.as_raw_fd(),
+ SOL_SOCKET,
+ SO_REUSEPORT,
+ &v as *const _ as *const _,
+ mem::size_of_val(&v) as u32,
+ );
+ }
+ }
+ }
+}
+
+#[cfg(windows)]
+mod imp {
+ use super::*;
+
+ use std::{
+ ffi::{CStr, OsString},
+ io, marker, mem,
+ net::UdpSocket,
+ os::{
+ raw::*,
+ windows::{ffi::OsStringExt, io::AsRawSocket},
+ },
+ ptr, slice,
+ };
+
+ use crate::{error::Context, ffi::windows::*};
+
+ /// Returns information for all non-loopback, multicast-capable network interfaces.
+ pub fn query_interfaces() -> Result<Vec<InterfaceInfo>, Error> {
+ struct AdapterAddresses {
+ addresses: ptr::NonNull<IP_ADAPTER_ADDRESSES_LH>,
+ heap: isize,
+ }
+
+ impl AdapterAddresses {
+ fn new() -> io::Result<Self> {
+ // SAFETY: Gets the process's default heap and is safe to be called at any time.
+ let heap = unsafe { GetProcessHeap() };
+
+ // SAFETY: GetAdaptersAddresses() requires allocated memory to be passed in.
+ // In the beginning 16kB are allocated via HeapAlloc() from the default process's
+ // heap (see above), then passed to GetAdaptersAddresses().
+ //
+ // If this returns ERROR_NOT_ENOUGH_MEMORY then this was not enough memory and the
+ // required amount is returned as out parameter. In that case we loop up to 10
+ // times, reallocate memory via HeapReAlloc() and try again.
+ //
+ // On other errors the memory is freed before returning via HeapFree(), or when 10
+ // iterations were reached.
+ unsafe {
+ let mut alloc_len = 16_384;
+ let mut tries = 0;
+ let mut addresses: *mut IP_ADAPTER_ADDRESSES_LH = ptr::null_mut();
+
+ loop {
+ if tries > 10 {
+ HeapFree(heap, 0, addresses as *mut _);
+ return Err(io::Error::from(io::ErrorKind::OutOfMemory));
+ }
+
+ if addresses.is_null() {
+ addresses = HeapAlloc(heap, 0, alloc_len as usize) as *mut _;
+ } else {
+ addresses =
+ HeapReAlloc(heap, 0, addresses as *mut _, alloc_len as usize)
+ as *mut _;
+ }
+ if addresses.is_null() {
+ return Err(io::Error::from(io::ErrorKind::OutOfMemory));
+ }
+
+ let res = GetAdaptersAddresses(
+ AF_INET,
+ GAA_FLAG_SKIP_ANYCAST
+ | GAA_FLAG_SKIP_MULTICAST
+ | GAA_FLAG_SKIP_DNS_SERVER,
+ ptr::null_mut(),
+ addresses,
+ &mut alloc_len,
+ );
+
+ if res == 0 {
+ return Ok(AdapterAddresses {
+ heap,
+ addresses: ptr::NonNull::new_unchecked(addresses),
+ });
+ } else if res == ERROR_NOT_ENOUGH_MEMORY {
+ tries += 1;
+ continue;
+ } else {
+ HeapFree(heap, 0, addresses as *mut _);
+ return Err(io::Error::from_raw_os_error(res as i32));
+ }
+ }
+ }
+ }
+
+ fn iter(&self) -> AdapterAddressesIter {
+ AdapterAddressesIter {
+ ptr: Some(self.addresses),
+ phantom: marker::PhantomData,
+ }
+ }
+ }
+
+ impl Drop for AdapterAddresses {
+ fn drop(&mut self) {
+ // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by construction
+ // and dropped only once, so freeing it here is OK. It might be NULL so check for
+ // that first.
+ //
+ // Heap is the process's heap as set in the constructor above.
+ unsafe {
+ HeapFree(self.heap, 0, self.addresses.as_ptr() as *mut _);
+ }
+ }
+ }
+
+ struct AdapterAddressesIter<'a> {
+ ptr: Option<ptr::NonNull<IP_ADAPTER_ADDRESSES_LH>>,
+ phantom: marker::PhantomData<&'a AdapterAddresses>,
+ }
+
+ impl<'a> Iterator for AdapterAddressesIter<'a> {
+ type Item = &'a IP_ADAPTER_ADDRESSES_LH;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.ptr {
+ None => None,
+ Some(ptr) => {
+ // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by
+ // construction so creating a reference to it is OK.
+ let addr = unsafe { &*ptr.as_ptr() };
+ self.ptr = ptr::NonNull::new(addr.next);
+ Some(addr)
+ }
+ }
+ }
+ }
+
+ struct UnicastAddressesIter<'a> {
+ ptr: Option<ptr::NonNull<IP_ADAPTER_UNICAST_ADDRESS_LH>>,
+ phantom: marker::PhantomData<&'a IP_ADAPTER_ADDRESSES_LH>,
+ }
+
+ impl<'a> UnicastAddressesIter<'a> {
+ fn new(addresses: &'a IP_ADAPTER_ADDRESSES_LH) -> Self {
+ Self {
+ ptr: ptr::NonNull::new(addresses.firstunicastaddress),
+ phantom: marker::PhantomData,
+ }
+ }
+ }
+
+ impl<'a> Iterator for UnicastAddressesIter<'a> {
+ type Item = &'a IP_ADAPTER_UNICAST_ADDRESS_LH;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.ptr {
+ None => None,
+ Some(ptr) => {
+ let addr = unsafe { &*ptr.as_ptr() };
+ self.ptr = ptr::NonNull::new(addr.next);
+ Some(addr)
+ }
+ }
+ }
+ }
+
+ let addresses = AdapterAddresses::new().context("Failed getting adapter addresses")?;
+ let mut if_infos = Vec::<InterfaceInfo>::new();
+ for address in addresses.iter() {
+ // SAFETY: adaptername points to a NUL-terminated ASCII name string that is valid
+ // as long as its struct is
+ let adaptername = unsafe { CStr::from_ptr(address.adaptername as *const c_char) }
+ .to_str()
+ .unwrap();
+
+ // Skip adapters that are receive-only, can't do multicast or don't have IPv4 support
+ // as they're not usable in a PTP context.
+ if address.flags & ADAPTER_FLAG_RECEIVE_ONLY != 0
+ || address.flags & ADAPTER_FLAG_NO_MULTICAST != 0
+ || address.flags & ADAPTER_FLAG_IPV4_ENABLED == 0
+ {
+ continue;
+ }
+
+ // Skip adapters that are loopback or not up.
+ if address.iftype == IF_TYPE_SOFTWARE_LOOPBACK
+ || address.operstatus != IF_OPER_STATUS_UP
+ {
+ continue;
+ }
+
+ // SAFETY: Both fields of the union are always valid
+ let index = unsafe { address.anonymous.anonymous.ifindex } as usize;
+ // Skip adapters that have no valid interface index as they can't be used to join the
+ // PTP multicast group reliably for this interface only.
+ if index == 0 {
+ continue;
+ }
+
+ // SAFETY: friendlyname is a NUL-terminated UCS2/wide string or NULL.
+ let friendlyname = unsafe {
+ if !address.friendlyname.is_null() {
+ let len = {
+ let mut len = 0;
+ while *address.friendlyname.add(len) != 0 {
+ len += 1;
+ }
+ len
+ };
+
+ let f = slice::from_raw_parts(address.friendlyname, len);
+ let f = OsString::from_wide(f);
+ Some(String::from(f.to_str().unwrap()))
+ } else {
+ None
+ }
+ };
+
+ let mut hw_addr = None;
+ if address.physicaladdresslength == 6 {
+ let mut addr = [0u8; 6];
+ addr.copy_from_slice(&address.physicaladdress[..6]);
+ hw_addr = Some(addr);
+ }
+
+ let ip_addr = UnicastAddressesIter::new(address).find_map(|addr| {
+ if addr.address.lpsocketaddr.is_null() {
+ return None;
+ }
+
+ // SAFETY: lpsocketaddr is a valid, non-NULL socket address and its family
+ // field can be read to distinguish IPv4 and other socket addresses
+ if unsafe { (*addr.address.lpsocketaddr).sa_family } != AF_INET as u16 {
+ return None;
+ }
+
+ Some(Ipv4Addr::from(
+ // SAFETY: lpsocketaddr is a valid, non-NULL IPv4 socket address as checked
+ // above and can be dereferenced as such.
+ unsafe {
+ (*addr.address.lpsocketaddr)
+ .in_addr
+ .S_un
+ .S_addr
+ .to_ne_bytes()
+ },
+ ))
+ });
+
+ if let Some(ip_addr) = ip_addr {
+ if_infos.push(InterfaceInfo {
+ name: String::from(adaptername),
+ other_name: friendlyname,
+ index,
+ ip_addr,
+ hw_addr,
+ });
+ }
+ }
+
+ Ok(if_infos)
+ }
+
+ // Join multicast address for a given interface.
+ pub fn join_multicast_v4(
+ socket: &UdpSocket,
+ addr: &Ipv4Addr,
+ iface: &InterfaceInfo,
+ ) -> Result<(), Error> {
+ let mreq = IP_MREQ {
+ imr_multiaddr: IN_ADDR {
+ S_un: IN_ADDR_0 {
+ S_addr: u32::from_ne_bytes(addr.octets()),
+ },
+ },
+ imr_address: IN_ADDR {
+ S_un: IN_ADDR_0 {
+ S_addr: u32::from_ne_bytes(Ipv4Addr::new(0, 0, 0, iface.index as u8).octets()),
+ },
+ },
+ };
+
+ // SAFETY: Requires a valid ip_mreq struct to be passed together with its size for checking
+ // validity. On errors a negative integer is returned.
+ unsafe {
+ if setsockopt(
+ socket.as_raw_socket(),
+ IPPROTO_IP as i32,
+ IP_ADD_MEMBERSHIP as i32,
+ &mreq as *const _ as *const _,
+ mem::size_of_val(&mreq) as _,
+ ) < 0
+ {
+ bail!(
+ source: io::Error::from_raw_os_error(WSAGetLastError()),
+ "Failed joining multicast group for interface {}",
+ iface.name,
+ );
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Allow multiple sockets to bind to the same address / port.
+ ///
+ /// This is best-effort and might not actually do anything.
+ pub fn set_reuse(socket: &UdpSocket) {
+ // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and
+ // enables the given feature on the socket.
+ //
+ // We explicitly ignore errors here. If it works, good, if it doesn't then not much
+ // lost other than the ability to run multiple processes at once.
+ unsafe {
+ let v = 1i32;
+ let _ = setsockopt(
+ socket.as_raw_socket(),
+ SOL_SOCKET as i32,
+ SO_REUSEADDR as i32,
+ &v as *const _ as *const _,
+ mem::size_of_val(&v) as _,
+ );
+ }
+ }
+}
+
+pub use imp::*;
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs
new file mode 100644
index 0000000000..66e5c954e1
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs
@@ -0,0 +1,169 @@
+// 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
+
+use crate::error::Error;
+
+/// Drop all additional permissions / capabilities the current process might have as they're not
+/// needed anymore.
+///
+/// This does nothing if no such mechanism is implemented / selected for the target platform.
+pub fn drop() -> Result<(), Error> {
+ #[cfg(ptp_helper_permissions = "setcap")]
+ {
+ // Drop all current capabilities of the process.
+
+ use std::io;
+
+ use crate::{bail, ffi::unix::setcaps::*};
+
+ struct Cap(cap_t);
+ impl Drop for Cap {
+ fn drop(&mut self) {
+ // SAFETY: The capabilities are valid by construction and are only dropped
+ // once here.
+ unsafe {
+ let _ = cap_free(self.0);
+ }
+ }
+ }
+
+ // SAFETY: There are 3 steps here
+ // 1. Get the current capabilities of the process. This
+ // returns NULL on error or otherwise newly allocated capabilities that have to be
+ // freed again in the end. For that purpose we wrap them in the Cap struct.
+ //
+ // 2. Clearing all current capabilities. This requires a valid capabilities pointer,
+ // which we have at this point by construction.
+ //
+ // 3. Setting the current process's capabilities, which is only affecting the current
+ // thread unfortunately. At this point, no other threads were started yet so this is
+ // not a problem. Also the capabilities pointer is still valid by construction.
+ //
+ // On every return path, the capabilities are going to be freed.
+ unsafe {
+ let c = cap_get_proc();
+ if c.is_null() {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed to get current process capabilities"
+ );
+ }
+
+ let c = Cap(c);
+ if cap_clear(c.0) != 0 {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed to clear capabilities"
+ );
+ }
+ if cap_set_proc(c.0) != 0 {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed to set current process capabilities"
+ );
+ }
+ }
+ }
+ #[cfg(ptp_helper_permissions = "setuid-root")]
+ {
+ // Drop the process's UID/GID from root to the configured user/group or the user "nobody".
+
+ use std::{ffi::CString, io};
+
+ use crate::{bail, error::Context, ffi::unix::setuid_root::*};
+
+ fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> {
+ let name_cstr = CString::new(name).unwrap();
+
+ loop {
+ // SAFETY: getpwnam() requires a NUL-terminated user name string and
+ // returns either the user information in static storage, or NULL on error.
+ // In case of EINTR, getting the user information can be retried.
+ //
+ // The user information is stored in static storage so might change if something
+ // else calls related functions. As this is the only thread up to this point and we
+ // just extract two integers from it there is no such possibility.
+ unsafe {
+ let pw = getpwnam(name_cstr.as_ptr());
+ if pw.is_null() {
+ let err = io::Error::last_os_error();
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue;
+ }
+ return Err(err);
+ }
+ return Ok(((*pw).pw_uid, (*pw).pw_gid));
+ }
+ }
+ }
+
+ fn get_gid_for_group(name: &str) -> io::Result<gid_t> {
+ let name_cstr = CString::new(name).unwrap();
+ loop {
+ // SAFETY: getgrnam() requires a NUL-terminated group name string and
+ // returns either the group information in static storage, or NULL on error.
+ // In case of EINTR, getting the group information can be retried.
+ //
+ // The user information is stored in static storage so might change if something
+ // else calls related functions. As this is the only thread up to this point and we
+ // just extract two integers from it there is no such possibility.
+ unsafe {
+ let grp = getgrnam(name_cstr.as_ptr());
+ if grp.is_null() {
+ let err = io::Error::last_os_error();
+ if err.kind() == io::ErrorKind::Interrupted {
+ continue;
+ }
+ return Err(err);
+ }
+
+ return Ok((*grp).gr_gid);
+ }
+ }
+ }
+
+ let username = gst_ptp_helper_conf::PTP_HELPER_SETUID_USER.unwrap_or("nobody");
+
+ let (uid, gid) = get_uid_gid_for_user(username)
+ .with_context(|| format!("Failed to get user information for {}", username))?;
+ let gid = if let Some(group) = gst_ptp_helper_conf::PTP_HELPER_SETUID_GROUP {
+ get_gid_for_group(group)
+ .with_context(|| format!("Failed to get group information for {}", group))?
+ } else {
+ gid
+ };
+
+ // SAFETY: This function can be called at any time and never fails.
+ let old_gid = unsafe { getgid() };
+
+ // SAFETY: Changes the effective group id of the process and return zero on success.
+ unsafe {
+ if setgid(gid) != 0 {
+ bail!(
+ source: io::Error::last_os_error(),
+ "Failed to set group id {} for process",
+ gid,
+ );
+ }
+ }
+
+ // SAFETY: Changes the effective user id of the process and return zero on success.
+ // On errors, try resetting to the old gid just to be sure.
+ unsafe {
+ if setuid(uid) != 0 {
+ let err = io::Error::last_os_error();
+ let _ = setgid(old_gid);
+ bail!(source: err, "Failed to set user id {} for process", uid);
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp_helper_post_install.sh b/subprojects/gstreamer/libs/gst/helpers/ptp/ptp_helper_post_install.sh
index 4370acd956..4370acd956 100755
--- a/subprojects/gstreamer/libs/gst/helpers/ptp_helper_post_install.sh
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/ptp_helper_post_install.sh
diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs
new file mode 100644
index 0000000000..48cda11ec5
--- /dev/null
+++ b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs
@@ -0,0 +1,150 @@
+// 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
+
+/// Returns a random'ish 64 bit value.
+pub fn rand() -> [u8; 8] {
+ #[cfg(unix)]
+ {
+ // Try getrandom syscall or otherwise first on Linux
+ #[cfg(target_os = "linux")]
+ {
+ use std::io::Read;
+
+ use crate::ffi::unix::linux::*;
+
+ // Depends on us knowing the syscall number
+ if SYS_getrandom != 0 {
+ struct GetRandom;
+
+ impl Read for GetRandom {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ // SAFETY: `getrandom` syscall fills up to the requested amount of bytes of
+ // the provided memory and returns the number of bytes or a negative value
+ // on errors.
+ unsafe {
+ let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32);
+ if res < 0 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(res as usize)
+ }
+ }
+ }
+ }
+
+ let mut r = [0u8; 8];
+ if GetRandom.read_exact(&mut r).is_ok() {
+ return r;
+ }
+ }
+ }
+
+ // Otherwise try /dev/urandom
+ {
+ use crate::ffi::unix::*;
+ use std::{io::Read, os::raw::c_int};
+
+ struct Fd(c_int);
+
+ impl Drop for Fd {
+ fn drop(&mut self) {
+ // SAFETY: The fd is valid by construction below and closed by this at
+ // most once.
+ unsafe {
+ // Return value is intentionally ignored as there's nothing that
+ // can be done on errors anyway.
+ let _ = close(self.0);
+ }
+ }
+ }
+
+ impl Read for Fd {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ // SAFETY: read() requires a valid fd and a mutable buffer with the given size.
+ // The fd is valid by construction as is the buffer.
+ //
+ // read() will return the number of bytes read or a negative value on errors.
+ let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) };
+ if res < 0 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(res as usize)
+ }
+ }
+ }
+
+ let fd = loop {
+ // SAFETY: open() requires a NUL-terminated file path and will
+ // return an integer in any case. A negative value is an invalid fd
+ // and signals an error. On EINTR, opening can be retried.
+ let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) };
+ if fd < 0 {
+ let err = std::io::Error::last_os_error();
+ if err.kind() == std::io::ErrorKind::Interrupted {
+ continue;
+ }
+
+ break None;
+ }
+
+ break Some(Fd(fd));
+ };
+
+ if let Some(mut fd) = fd {
+ let mut r = [0u8; 8];
+
+ if fd.read_exact(&mut r).is_ok() {
+ return r;
+ }
+ }
+ }
+ }
+ #[cfg(windows)]
+ {
+ // Try BCryptGenRandom(), which is available since Windows Vista
+ //
+ // SAFETY: BCryptGenRandom() fills the provided memory with the requested number of bytes
+ // and returns 0 on success. In that case, all memory was written and is initialized now.
+ unsafe {
+ use std::{mem, ptr};
+
+ use crate::ffi::windows::*;
+
+ let mut r = mem::MaybeUninit::<[u8; 8]>::uninit();
+ let res = BCryptGenRandom(
+ ptr::null_mut(),
+ r.as_mut_ptr() as *mut u8,
+ 8,
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG,
+ );
+ if res == 0 {
+ return r.assume_init();
+ }
+ }
+ }
+
+ // As fallback use a combination of the process ID and the current system time
+ let now = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos()
+ .to_be_bytes();
+ let pid = std::process::id().to_be_bytes();
+ [
+ now[0] ^ now[15] ^ pid[0],
+ now[1] ^ now[14] ^ pid[1],
+ now[2] ^ now[13] ^ pid[2],
+ now[3] ^ now[12] ^ pid[3],
+ now[4] ^ now[11] ^ pid[0],
+ now[5] ^ now[10] ^ pid[1],
+ now[6] ^ now[9] ^ pid[2],
+ now[7] ^ now[8] ^ pid[3],
+ ]
+}