/* GStreamer * Copyright (C) 2015 Sebastian Dröge * * * 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 #include #include #include #include #include #include #include #include #ifdef HAVE_GETIFADDRS_AF_LINK #include #include #endif #ifdef HAVE_PTP_HELPER_SETUID #include #include #endif #ifdef HAVE_PTP_HELPER_CAPABILITIES #include #endif #include #include #include #include #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); g_socket_set_multicast_loopback (socket_event, FALSE); 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); g_socket_set_multicast_loopback (socket_general, FALSE); /* 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; }