udpsrc: Filter out multicast packets that are not for our multicast address
authorSebastian Dröge <sebastian@centricular.com>
Thu, 23 Jun 2016 17:21:59 +0000 (20:21 +0300)
committerSebastian Dröge <sebastian@centricular.com>
Tue, 28 Jun 2016 10:40:06 +0000 (13:40 +0300)
https://bugzilla.gnome.org/show_bug.cgi?id=767980

gst/udp/gstudpsrc.c

index ac57770..f2ccca4 100644 (file)
 #include "config.h"
 #endif
 
+/* Needed to get struct in6_pktinfo */
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
 #include <string.h>
 #include "gstudpsrc.h"
 
 
 #include <gio/gnetworking.h>
 
+/* Control messages for getting the destination address */
+#ifdef IP_PKTINFO
+GType gst_ip_pktinfo_message_get_type (void);
+
+#define GST_TYPE_IP_PKTINFO_MESSAGE         (gst_ip_pktinfo_message_get_type ())
+#define GST_IP_PKTINFO_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GST_TYPE_IP_PKTINFO_MESSAGE, GstIPPktinfoMessage))
+#define GST_IP_PKTINFO_MESSAGE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), GST_TYPE_IP_PKTINFO_MESSAGE, GstIPPktinfoMessageClass))
+#define GST_IS_IP_PKTINFO_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GST_TYPE_IP_PKTINFO_MESSAGE))
+#define GST_IS_IP_PKTINFO_MESSAGE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), GST_TYPE_IP_PKTINFO_MESSAGE))
+#define GST_IP_PKTINFO_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GST_TYPE_IP_PKTINFO_MESSAGE, GstIPPktinfoMessageClass))
+
+typedef struct _GstIPPktinfoMessage GstIPPktinfoMessage;
+typedef struct _GstIPPktinfoMessageClass GstIPPktinfoMessageClass;
+
+struct _GstIPPktinfoMessageClass
+{
+  GSocketControlMessageClass parent_class;
+
+};
+
+struct _GstIPPktinfoMessage
+{
+  GSocketControlMessage parent;
+
+  guint ifindex;
+  struct in_addr spec_dst, addr;
+};
+
+G_DEFINE_TYPE (GstIPPktinfoMessage, gst_ip_pktinfo_message,
+    G_TYPE_SOCKET_CONTROL_MESSAGE);
+
+static gsize
+gst_ip_pktinfo_message_get_size (GSocketControlMessage * message)
+{
+  return sizeof (struct in_pktinfo);
+}
+
+static int
+gst_ip_pktinfo_message_get_level (GSocketControlMessage * message)
+{
+  return IPPROTO_IP;
+}
+
+static int
+gst_ip_pktinfo_message_get_msg_type (GSocketControlMessage * message)
+{
+  return IP_PKTINFO;
+}
+
+static GSocketControlMessage *
+gst_ip_pktinfo_message_deserialize (gint level,
+    gint type, gsize size, gpointer data)
+{
+  struct in_pktinfo *pktinfo;
+  GstIPPktinfoMessage *message;
+
+  if (level != IPPROTO_IP || type != IP_PKTINFO)
+    return NULL;
+
+  if (size < sizeof (struct in_pktinfo))
+    return NULL;
+
+  pktinfo = data;
+
+  message = g_object_new (GST_TYPE_IP_PKTINFO_MESSAGE, NULL);
+  message->ifindex = pktinfo->ipi_ifindex;
+  message->spec_dst = pktinfo->ipi_spec_dst;
+  message->addr = pktinfo->ipi_addr;
+
+  return G_SOCKET_CONTROL_MESSAGE (message);
+}
+
+static void
+gst_ip_pktinfo_message_init (GstIPPktinfoMessage * message)
+{
+}
+
+static void
+gst_ip_pktinfo_message_class_init (GstIPPktinfoMessageClass * class)
+{
+  GSocketControlMessageClass *scm_class;
+
+  scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class);
+  scm_class->get_size = gst_ip_pktinfo_message_get_size;
+  scm_class->get_level = gst_ip_pktinfo_message_get_level;
+  scm_class->get_type = gst_ip_pktinfo_message_get_msg_type;
+  scm_class->deserialize = gst_ip_pktinfo_message_deserialize;
+}
+#endif
+
+#ifdef IPV6_PKTINFO
+GType gst_ipv6_pktinfo_message_get_type (void);
+
+#define GST_TYPE_IPV6_PKTINFO_MESSAGE         (gst_ipv6_pktinfo_message_get_type ())
+#define GST_IPV6_PKTINFO_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GST_TYPE_IPV6_PKTINFO_MESSAGE, GstIPV6PktinfoMessage))
+#define GST_IPV6_PKTINFO_MESSAGE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), GST_TYPE_IPV6_PKTINFO_MESSAGE, GstIPV6PktinfoMessageClass))
+#define GST_IS_IPV6_PKTINFO_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GST_TYPE_IPV6_PKTINFO_MESSAGE))
+#define GST_IS_IPV6_PKTINFO_MESSAGE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), GST_TYPE_IPV6_PKTINFO_MESSAGE))
+#define GST_IPV6_PKTINFO_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GST_TYPE_IPV6_PKTINFO_MESSAGE, GstIPV6PktinfoMessageClass))
+
+typedef struct _GstIPV6PktinfoMessage GstIPV6PktinfoMessage;
+typedef struct _GstIPV6PktinfoMessageClass GstIPV6PktinfoMessageClass;
+
+struct _GstIPV6PktinfoMessageClass
+{
+  GSocketControlMessageClass parent_class;
+
+};
+
+struct _GstIPV6PktinfoMessage
+{
+  GSocketControlMessage parent;
+
+  guint ifindex;
+  struct in6_addr addr;
+};
+
+G_DEFINE_TYPE (GstIPV6PktinfoMessage, gst_ipv6_pktinfo_message,
+    G_TYPE_SOCKET_CONTROL_MESSAGE);
+
+static gsize
+gst_ipv6_pktinfo_message_get_size (GSocketControlMessage * message)
+{
+  return sizeof (struct in6_pktinfo);
+}
+
+static int
+gst_ipv6_pktinfo_message_get_level (GSocketControlMessage * message)
+{
+  return IPPROTO_IPV6;
+}
+
+static int
+gst_ipv6_pktinfo_message_get_msg_type (GSocketControlMessage * message)
+{
+  return IPV6_PKTINFO;
+}
+
+static GSocketControlMessage *
+gst_ipv6_pktinfo_message_deserialize (gint level,
+    gint type, gsize size, gpointer data)
+{
+  struct in6_pktinfo *pktinfo;
+  GstIPV6PktinfoMessage *message;
+
+  if (level != IPPROTO_IPV6 || type != IPV6_PKTINFO)
+    return NULL;
+
+  if (size < sizeof (struct in_pktinfo))
+    return NULL;
+
+  pktinfo = data;
+
+  message = g_object_new (GST_TYPE_IPV6_PKTINFO_MESSAGE, NULL);
+  message->ifindex = pktinfo->ipi6_ifindex;
+  message->addr = pktinfo->ipi6_addr;
+
+  return G_SOCKET_CONTROL_MESSAGE (message);
+}
+
+static void
+gst_ipv6_pktinfo_message_init (GstIPV6PktinfoMessage * message)
+{
+}
+
+static void
+gst_ipv6_pktinfo_message_class_init (GstIPV6PktinfoMessageClass * class)
+{
+  GSocketControlMessageClass *scm_class;
+
+  scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class);
+  scm_class->get_size = gst_ipv6_pktinfo_message_get_size;
+  scm_class->get_level = gst_ipv6_pktinfo_message_get_level;
+  scm_class->get_type = gst_ipv6_pktinfo_message_get_msg_type;
+  scm_class->deserialize = gst_ipv6_pktinfo_message_deserialize;
+}
+
+#endif
+
+#ifdef IP_RECVDSTADDR
+GType gst_ip_recvdstaddr_message_get_type (void);
+
+#define GST_TYPE_IP_RECVDSTADDR_MESSAGE         (gst_ip_recvdstaddr_message_get_type ())
+#define GST_IP_RECVDSTADDR_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GST_TYPE_IP_RECVDSTADDR_MESSAGE, GstIPRecvdstaddrMessage))
+#define GST_IP_RECVDSTADDR_MESSAGE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), GST_TYPE_IP_RECVDSTADDR_MESSAGE, GstIPRecvdstaddrMessageClass))
+#define GST_IS_IP_RECVDSTADDR_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GST_TYPE_IP_RECVDSTADDR_MESSAGE))
+#define GST_IS_IP_RECVDSTADDR_MESSAGE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), GST_TYPE_IP_RECVDSTADDR_MESSAGE))
+#define GST_IP_RECVDSTADDR_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GST_TYPE_IP_RECVDSTADDR_MESSAGE, GstIPRecvdstaddrMessageClass))
+
+typedef struct _GstIPRecvdstaddrMessage GstIPRecvdstaddrMessage;
+typedef struct _GstIPRecvdstaddrMessageClass GstIPRecvdstaddrMessageClass;
+
+struct _GstIPRecvdstaddrMessageClass
+{
+  GSocketControlMessageClass parent_class;
+
+};
+
+struct _GstIPRecvdstaddrMessage
+{
+  GSocketControlMessage parent;
+
+  guint ifindex;
+  struct in_addr addr;
+};
+
+G_DEFINE_TYPE (GstIPRecvdstaddrMessage, gst_ip_recvdstaddr_message,
+    G_TYPE_SOCKET_CONTROL_MESSAGE);
+
+static gsize
+gst_ip_recvdstaddr_message_get_size (GSocketControlMessage * message)
+{
+  return sizeof (struct in_addr);
+}
+
+static int
+gst_ip_recvdstaddr_message_get_level (GSocketControlMessage * message)
+{
+  return IPPROTO_IP;
+}
+
+static int
+gst_ip_recvdstaddr_message_get_msg_type (GSocketControlMessage * message)
+{
+  return IP_RECVDSTADDR;
+}
+
+static GSocketControlMessage *
+gst_ip_recvdstaddr_message_deserialize (gint level,
+    gint type, gsize size, gpointer data)
+{
+  struct in_addr *addr;
+  GstIPRecvdstaddrMessage *message;
+
+  if (level != IPPROTO_IP || type != IP_RECVDSTADDR)
+    return NULL;
+
+  if (size < sizeof (struct in_addr))
+    return NULL;
+
+  addr = data;
+
+  message = g_object_new (GST_TYPE_IP_RECVDSTADDR_MESSAGE, NULL);
+  message->addr = g_inet_address_new_from_bytes ((guint8 *) addr, AF_INET);
+
+  return G_SOCKET_CONTROL_MESSAGE (message);
+}
+
+static void
+gst_ip_recvdstaddr_message_init (GstIPRecvdstaddrMessage * message)
+{
+}
+
+static void
+gst_ip_recvdstaddr_message_class_init (GstIPRecvdstaddrMessageClass * class)
+{
+  GSocketControlMessageClass *scm_class;
+
+  scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class);
+  scm_class->get_size = gst_ip_recvdstaddr_message_get_size;
+  scm_class->get_level = gst_ip_recvdstaddr_message_get_level;
+  scm_class->get_type = gst_ip_recvdstaddr_message_get_msg_type;
+  scm_class->deserialize = gst_ip_recvdstaddr_message_deserialize;
+}
+#endif
+
 /* not 100% correct, but a good upper bound for memory allocation purposes */
 #define MAX_IPV4_UDP_PACKET_SIZE (65536 - 8)
 
@@ -201,6 +473,16 @@ gst_udpsrc_class_init (GstUDPSrcClass * klass)
 
   GST_DEBUG_CATEGORY_INIT (udpsrc_debug, "udpsrc", 0, "UDP src");
 
+#ifdef IP_PKTINFO
+  GST_TYPE_IP_PKTINFO_MESSAGE;
+#endif
+#ifdef IPV6_PKTINFO
+  GST_TYPE_IPV6_PKTINFO_MESSAGE;
+#endif
+#ifdef IP_RECVDSTADDR
+  GST_TYPE_IP_RECVDSTADDR_MESSAGE;
+#endif
+
   gobject_class->set_property = gst_udpsrc_set_property;
   gobject_class->get_property = gst_udpsrc_get_property;
   gobject_class->finalize = gst_udpsrc_finalize;
@@ -543,6 +825,8 @@ gst_udpsrc_create (GstPushSrc * psrc, GstBuffer ** buf)
   GError *err = NULL;
   gssize res;
   gsize offset;
+  GSocketControlMessage **msgs = NULL;
+  gint n_msgs = 0, i;
 
   udpsrc = GST_UDPSRC_CAST (psrc);
 
@@ -593,7 +877,7 @@ retry:
 
   res =
       g_socket_receive_message (udpsrc->used_socket, p_saddr, udpsrc->vec, 2,
-      NULL, NULL, &flags, udpsrc->cancellable, &err);
+      &msgs, &n_msgs, &flags, udpsrc->cancellable, &err);
 
   if (G_UNLIKELY (res < 0)) {
     /* G_IO_ERROR_HOST_UNREACHABLE for a UDP socket means that a packet sent
@@ -616,6 +900,66 @@ retry:
   if (res > udpsrc->max_size)
     udpsrc->max_size = res;
 
+  /* Retry if multicast and the destination address is not ours. We don't want
+   * to receive arbitrary packets */
+  {
+    GInetAddress *iaddr = g_inet_socket_address_get_address (udpsrc->addr);
+    gboolean skip_packet = FALSE;
+    gsize iaddr_size = g_inet_address_get_native_size (iaddr);
+    const guint8 *iaddr_bytes = g_inet_address_to_bytes (iaddr);
+
+    if (g_inet_address_get_is_multicast (iaddr)) {
+
+      for (i = 0; i < n_msgs && !skip_packet; i++) {
+#ifdef IP_PKTINFO
+        if (GST_IS_IP_PKTINFO_MESSAGE (msgs[i])) {
+          GstIPPktinfoMessage *msg = GST_IP_PKTINFO_MESSAGE (msgs[i]);
+
+          if (sizeof (msg->addr) == iaddr_size
+              && memcmp (iaddr_bytes, &msg->addr, sizeof (msg->addr)))
+            skip_packet = TRUE;
+        }
+#endif
+#ifdef IPV6_PKTINFO
+        if (GST_IS_IPV6_PKTINFO_MESSAGE (msgs[i])) {
+          GstIPV6PktinfoMessage *msg = GST_IPV6_PKTINFO_MESSAGE (msgs[i]);
+
+          if (sizeof (msg->addr) == iaddr_size
+              && memcmp (iaddr_bytes, &msg->addr, sizeof (msg->addr)))
+            skip_packet = TRUE;
+        }
+#endif
+#ifdef IP_RECVDSTADDR
+        if (GST_IS_IP_RECVDSTADDR_MESSAGE (msgs[i])) {
+          GstIPRecvdstaddrMessage *msg = GST_IP_RECVDSTADDR_MESSAGE (msgs[i]);
+
+          if (sizeof (msg->addr) == iaddr_size
+              && memcmp (iaddr_bytes, &msg->addr, sizeof (msg->addr)))
+            skip_packet = TRUE;
+        }
+#endif
+      }
+
+    }
+
+    for (i = 0; i < n_msgs; i++) {
+      g_object_unref (msgs[i]);
+    }
+    g_free (msgs);
+
+    if (skip_packet) {
+      GST_DEBUG_OBJECT (udpsrc,
+          "Dropping packet for a different multicast address");
+
+      if (saddr != NULL) {
+        g_object_unref (saddr);
+        saddr = NULL;
+      }
+
+      goto retry;
+    }
+  }
+
   outbuf = gst_buffer_new ();
 
   /* append first memory chunk to buffer */
@@ -1075,6 +1419,49 @@ gst_udpsrc_open (GstUDPSrc * src)
             g_inet_socket_address_get_address (src->addr),
             FALSE, src->multi_iface, &err))
       goto membership;
+
+    if (g_inet_address_get_family (g_inet_socket_address_get_address
+            (src->addr)) == G_SOCKET_FAMILY_IPV4) {
+#if defined(IP_PKTINFO)
+      if (!g_socket_set_option (src->used_socket, IPPROTO_IP, IP_PKTINFO, TRUE,
+              &err)) {
+        GST_WARNING_OBJECT (src, "Failed to enable IP_PKTINFO: %s",
+            err->message);
+        g_clear_error (&err);
+      }
+#elif defined(IP_RECVDSTADDR)
+      if (!g_socket_set_option (src->used_socket, IPPROTO_IP, IP_RECVDSTADDR,
+              TRUE, &err)) {
+        GST_WARNING_OBJECT (src, "Failed to enable IP_RECVDSTADDR: %s",
+            err->message);
+        g_clear_error (&err);
+      }
+#else
+#pragma message("No API available for getting IPv4 destination address")
+      GST_WARNING_OBJECT (src, "No API available for getting IPv4 destination "
+          "address, will receive packets for every destination to our port");
+#endif
+    } else
+        if (g_inet_address_get_family (g_inet_socket_address_get_address
+            (src->addr)) == G_SOCKET_FAMILY_IPV6) {
+#ifdef IPV6_PKTINFO
+#ifdef IPV6_RECVPKTINFO
+      if (!g_socket_set_option (src->used_socket, IPPROTO_IPV6,
+              IPV6_RECVPKTINFO, TRUE, &err)) {
+#else
+      if (!g_socket_set_option (src->used_socket, IPPROTO_IPV6, IPV6_PKTINFO,
+              TRUE, &err)) {
+#endif
+        GST_WARNING_OBJECT (src, "Failed to enable IPV6_PKTINFO: %s",
+            err->message);
+        g_clear_error (&err);
+      }
+#else
+#pragma message("No API available for getting IPv6 destination address")
+      GST_WARNING_OBJECT (src, "No API available for getting IPv6 destination "
+          "address, will receive packets for every destination to our port");
+#endif
+    }
   }
 
   /* NOTE: sockaddr_in.sin_port works for ipv4 and ipv6 because sin_port