diff --git a/gst/udp/gstudpsrc.c b/gst/udp/gstudpsrc.c index ac57770ce5..f2ccca4d4e 100644 --- a/gst/udp/gstudpsrc.c +++ b/gst/udp/gstudpsrc.c @@ -107,6 +107,12 @@ #include "config.h" #endif +/* Needed to get struct in6_pktinfo */ +#define _GNU_SOURCE +#include +#include +#include + #include #include "gstudpsrc.h" @@ -114,6 +120,272 @@ #include +/* 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