[glib] gio: add g_socket_join_multicast_group_ssm (IGMPv3 SSM)



commit ea725a64147b6af10166d56da1950a9a86d62610
Author: Julien Isorce <julien isorce gmail com>
Date:   Thu Nov 27 00:04:25 2014 +0000

    gio: add g_socket_join_multicast_group_ssm (IGMPv3 SSM)
    
    It adds support for source-specific multicast IGMPv3.
    
    Allow receiving data only from a specified source when joining
    a multicast group.
    
    g_socket_join_multicast_group_ssm can be called multiple times
    to allow receiving data from more than one source.
    
    Support IPv4 and IPv6.
    
    Fixes https://bugzilla.gnome.org/show_bug.cgi?id=740791

 config.h.meson                      |    3 +
 configure.ac                        |   16 ++
 docs/reference/gio/gio-sections.txt |    2 +
 gio/gsocket.c                       |  287 +++++++++++++++++++++++++++++++++++
 gio/gsocket.h                       |   12 ++
 gio/meson.build                     |   12 ++
 6 files changed, 332 insertions(+), 0 deletions(-)
---
diff --git a/config.h.meson b/config.h.meson
index 6f0b7e4..c67e179 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -364,6 +364,9 @@
 /* Define if you have the 'sig_atomic_t' type. */
 #mesondefine HAVE_SIG_ATOMIC_T
 
+/* Define if there is support for the ioctl request SIOCGIFADDR. */
+#mesondefine HAVE_SIOCGIFADDR
+
 /* Define to 1 if you have the `snprintf' function. */
 #mesondefine HAVE_SNPRINTF
 
diff --git a/configure.ac b/configure.ac
index c0825d7..9cd1510 100644
--- a/configure.ac
+++ b/configure.ac
@@ -951,6 +951,22 @@ fi
 
 AC_CHECK_FUNCS(endservent if_nametoindex if_indextoname sendmmsg recvmmsg)
 
+AC_MSG_CHECKING([for SIOCGIFADDR])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+    [[
+        #include <sys/ioctl.h>
+        #include <net/if.h>
+    ]],
+    [[
+        struct ifreq ifr;
+        ioctl(0, SIOCGIFADDR, &ifr);
+    ]])], [
+        AC_MSG_RESULT(yes)
+        AC_DEFINE(HAVE_SIOCGIFADDR, 1, [SIOCGIFADDR is available])
+    ], [
+        AC_MSG_RESULT(no)
+])
+
 AS_IF([test $glib_native_win32 = yes], [
   # <wspiapi.h> in the Windows SDK and in mingw-w64 has wrappers for
   # inline workarounds for getaddrinfo, getnameinfo and freeaddrinfo if
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index c21d71e..484ef62 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -2095,6 +2095,8 @@ g_socket_get_credentials
 <SUBSECTION>
 g_socket_join_multicast_group
 g_socket_leave_multicast_group
+g_socket_join_multicast_group_ssm
+g_socket_leave_multicast_group_ssm
 g_socket_get_multicast_loopback
 g_socket_set_multicast_loopback
 g_socket_get_multicast_ttl
diff --git a/gio/gsocket.c b/gio/gsocket.c
index 2d4669b..c4147fb 100644
--- a/gio/gsocket.c
+++ b/gio/gsocket.c
@@ -44,6 +44,10 @@
 # include <sys/ioctl.h>
 #endif
 
+#ifdef HAVE_SIOCGIFADDR
+#include <net/if.h>
+#endif
+
 #ifdef HAVE_SYS_FILIO_H
 # include <sys/filio.h>
 #endif
@@ -59,6 +63,7 @@
 #include "gdatagrambased.h"
 #include "gioenumtypes.h"
 #include "ginetaddress.h"
+#include "ginetsocketaddress.h"
 #include "ginitable.h"
 #include "gioerror.h"
 #include "gioenums.h"
@@ -2272,6 +2277,9 @@ g_socket_multicast_group_operation (GSocket       *socket,
  * in RFC 4604 is used. Note that on older platforms this may fail
  * with a %G_IO_ERROR_NOT_SUPPORTED error.
  *
+ * To bind to a given source-specific multicast address, use
+ * g_socket_join_multicast_group_ssm() instead.
+ *
  * Returns: %TRUE on success, %FALSE on error.
  *
  * Since: 2.32
@@ -2301,6 +2309,9 @@ g_socket_join_multicast_group (GSocket       *socket,
  * @socket remains bound to its address and port, and can still receive
  * unicast messages after calling this.
  *
+ * To unbind to a given source-specific multicast address, use
+ * g_socket_leave_multicast_group_ssm() instead.
+ *
  * Returns: %TRUE on success, %FALSE on error.
  *
  * Since: 2.32
@@ -2315,6 +2326,282 @@ g_socket_leave_multicast_group (GSocket       *socket,
   return g_socket_multicast_group_operation (socket, group, source_specific, iface, FALSE, error);
 }
 
+static gboolean
+g_socket_multicast_group_operation_ssm (GSocket       *socket,
+                                        GInetAddress  *group,
+                                        GInetAddress  *source_specific,
+                                        const gchar   *iface,
+                                        gboolean       join_group,
+                                        GError       **error)
+{
+  gint result;
+
+  g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
+  g_return_val_if_fail (socket->priv->type == G_SOCKET_TYPE_DATAGRAM, FALSE);
+  g_return_val_if_fail (G_IS_INET_ADDRESS (group), FALSE);
+  g_return_val_if_fail (iface == NULL || *iface != '\0', FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (!source_specific)
+    {
+      return g_socket_multicast_group_operation (socket, group, FALSE, iface,
+                                                 join_group, error);
+    }
+
+  if (!check_socket (socket, error))
+    return FALSE;
+
+  switch (g_inet_address_get_family (group))
+    {
+    case G_SOCKET_FAMILY_INVALID:
+    case G_SOCKET_FAMILY_UNIX:
+      {
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+            join_group ?
+            _("Error joining multicast group: %s") :
+            _("Error leaving multicast group: %s"),
+            _("Unsupported socket family"));
+        return FALSE;
+      }
+      break;
+
+    case G_SOCKET_FAMILY_IPV4:
+      {
+#ifdef IP_ADD_SOURCE_MEMBERSHIP
+        gint optname;
+        struct ip_mreq_source mc_req_src;
+
+        if (g_inet_address_get_family (source_specific) !=
+            G_SOCKET_FAMILY_IPV4)
+          {
+            g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                join_group ?
+                _("Error joining multicast group: %s") :
+                _("Error leaving multicast group: %s"),
+                _("source-specific not an IPv4 address"));
+            return FALSE;
+          }
+
+        memset (&mc_req_src, 0, sizeof (mc_req_src));
+
+        /* By default use the default IPv4 multicast interface. */
+        mc_req_src.imr_interface.s_addr = g_htonl (INADDR_ANY);
+
+        if (iface)
+          {
+#if defined(G_OS_WIN32) && defined (HAVE_IF_NAMETOINDEX)
+            guint iface_index = if_nametoindex (iface);
+            if (iface_index == 0)
+              {
+                int errsv = errno;
+
+                g_set_error (error, G_IO_ERROR,  g_io_error_from_errno (errsv),
+                             _("Interface not found: %s"), g_strerror (errsv));
+                return FALSE;
+              }
+            /* (0.0.0.iface_index) only works on Windows. */
+            mc_req_src.imr_interface.s_addr = g_htonl (iface_index);
+#elif defined (HAVE_SIOCGIFADDR)
+            int ret;
+            struct ifreq ifr;
+            struct sockaddr_in *iface_addr;
+            size_t if_name_len = strlen (iface);
+
+            memset (&ifr, 0, sizeof (ifr));
+
+            if (if_name_len >= sizeof (ifr.ifr_name))
+              {
+                g_set_error (error, G_IO_ERROR,  G_IO_ERROR_FILENAME_TOO_LONG,
+                             _("Interface name too long"));
+                return FALSE;
+              }
+
+            memcpy (ifr.ifr_name, iface, if_name_len);
+
+            /* Get the IPv4 address of the given network interface name. */
+            ret = ioctl (socket->priv->fd, SIOCGIFADDR, &ifr);
+            if (ret < 0)
+              {
+                int errsv = errno;
+
+                g_set_error (error, G_IO_ERROR,  g_io_error_from_errno (errsv),
+                             _("Interface not found: %s"), g_strerror (errsv));
+                return FALSE;
+              }
+
+            iface_addr = (struct sockaddr_in *) &ifr.ifr_addr;
+            mc_req_src.imr_interface.s_addr = iface_addr->sin_addr.s_addr;
+#endif  /* defined(G_OS_WIN32) && defined (HAVE_IF_NAMETOINDEX) */
+          }
+        memcpy (&mc_req_src.imr_multiaddr, g_inet_address_to_bytes (group),
+                g_inet_address_get_native_size (group));
+        memcpy (&mc_req_src.imr_sourceaddr,
+                g_inet_address_to_bytes (source_specific),
+                g_inet_address_get_native_size (source_specific));
+
+        optname =
+            join_group ? IP_ADD_SOURCE_MEMBERSHIP : IP_DROP_SOURCE_MEMBERSHIP;
+        result = setsockopt (socket->priv->fd, IPPROTO_IP, optname,
+                             &mc_req_src, sizeof (mc_req_src));
+#else
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+            join_group ?
+            _("Error joining multicast group: %s") :
+            _("Error leaving multicast group: %s"),
+            _("No support for IPv4 source-specific multicast"));
+        return FALSE;
+#endif  /* IP_ADD_SOURCE_MEMBERSHIP */
+      }
+      break;
+
+    case G_SOCKET_FAMILY_IPV6:
+      {
+#ifdef MCAST_JOIN_SOURCE_GROUP
+        gboolean res;
+        gint optname;
+        struct group_source_req mc_req_src;
+        GSocketAddress *saddr_group, *saddr_source_specific;
+        guint iface_index = 0;
+
+#if defined (HAVE_IF_NAMETOINDEX)
+        if (iface)
+          {
+            iface_index = if_nametoindex (iface);
+            if (iface_index == 0)
+              {
+                int errsv = errno;
+
+                g_set_error (error, G_IO_ERROR,  g_io_error_from_errno (errsv),
+                             _("Interface not found: %s"), g_strerror (errsv));
+                return FALSE;
+              }
+          }
+#endif  /* defined (HAVE_IF_NAMETOINDEX) */
+        mc_req_src.gsr_interface = iface_index;
+
+        saddr_group = g_inet_socket_address_new (group, 0);
+        res = g_socket_address_to_native (saddr_group, &mc_req_src.gsr_group,
+                                          sizeof (mc_req_src.gsr_group),
+                                          error);
+        g_object_unref (saddr_group);
+        if (!res)
+          return FALSE;
+
+        saddr_source_specific = g_inet_socket_address_new (source_specific, 0);
+        res = g_socket_address_to_native (saddr_source_specific,
+                                          &mc_req_src.gsr_source,
+                                          sizeof (mc_req_src.gsr_source),
+                                          error);
+        g_object_unref (saddr_source_specific);
+
+        if (!res)
+          return FALSE;
+
+        optname =
+            join_group ? MCAST_JOIN_SOURCE_GROUP : MCAST_LEAVE_SOURCE_GROUP;
+        result = setsockopt (socket->priv->fd, IPPROTO_IPV6, optname,
+                             &mc_req_src, sizeof (mc_req_src));
+#else
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+            join_group ?
+            _("Error joining multicast group: %s") :
+            _("Error leaving multicast group: %s"),
+            _("No support for IPv6 source-specific multicast"));
+        return FALSE;
+#endif  /* MCAST_JOIN_SOURCE_GROUP */
+      }
+      break;
+
+    default:
+      g_return_val_if_reached (FALSE);
+    }
+
+  if (result < 0)
+    {
+      int errsv = get_socket_errno ();
+
+      g_set_error (error, G_IO_ERROR, socket_io_error_from_errno (errsv),
+          join_group ?
+          _("Error joining multicast group: %s") :
+          _("Error leaving multicast group: %s"),
+           socket_strerror (errsv));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * g_socket_join_multicast_group_ssm:
+ * @socket: a #GSocket.
+ * @group: a #GInetAddress specifying the group address to join.
+ * @source_specific: (nullable): a #GInetAddress specifying the
+ * source-specific multicast address or %NULL to ignore.
+ * @iface: (nullable): Name of the interface to use, or %NULL
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Registers @socket to receive multicast messages sent to @group.
+ * @socket must be a %G_SOCKET_TYPE_DATAGRAM socket, and must have
+ * been bound to an appropriate interface and port with
+ * g_socket_bind().
+ *
+ * If @iface is %NULL, the system will automatically pick an interface
+ * to bind to based on @group.
+ *
+ * If @source_specific is not %NULL, use source-specific multicast as
+ * defined in RFC 4604. Note that on older platforms this may fail
+ * with a %G_IO_ERROR_NOT_SUPPORTED error.
+ *
+ * Note that this function can be called multiple times for the same
+ * @group with different @source_specific in order to receive multicast
+ * packets from more than one source.
+ *
+ * Returns: %TRUE on success, %FALSE on error.
+ *
+ * Since: 2.56
+ */
+gboolean
+g_socket_join_multicast_group_ssm (GSocket       *socket,
+                                   GInetAddress  *group,
+                                   GInetAddress  *source_specific,
+                                   const gchar   *iface,
+                                   GError       **error)
+{
+  return g_socket_multicast_group_operation_ssm (socket, group,
+      source_specific, iface, TRUE, error);
+}
+
+/**
+ * g_socket_leave_multicast_group_ssm:
+ * @socket: a #GSocket.
+ * @group: a #GInetAddress specifying the group address to leave.
+ * @source_specific: (nullable): a #GInetAddress specifying the
+ * source-specific multicast address or %NULL to ignore.
+ * @iface: (nullable): Name of the interface to use, or %NULL
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Removes @socket from the multicast group defined by @group, @iface,
+ * and @source_specific (which must all have the same values they had
+ * when you joined the group).
+ *
+ * @socket remains bound to its address and port, and can still receive
+ * unicast messages after calling this.
+ *
+ * Returns: %TRUE on success, %FALSE on error.
+ *
+ * Since: 2.56
+ */
+gboolean
+g_socket_leave_multicast_group_ssm (GSocket       *socket,
+                                    GInetAddress  *group,
+                                    GInetAddress  *source_specific,
+                                    const gchar   *iface,
+                                    GError       **error)
+{
+  return g_socket_multicast_group_operation_ssm (socket, group,
+      source_specific, iface, FALSE, error);
+}
+
 /**
  * g_socket_speaks_ipv4:
  * @socket: a #GSocket
diff --git a/gio/gsocket.h b/gio/gsocket.h
index 613c8dd..a65cbc2 100644
--- a/gio/gsocket.h
+++ b/gio/gsocket.h
@@ -157,6 +157,18 @@ gboolean               g_socket_leave_multicast_group   (GSocket
                                                          gboolean                 source_specific,
                                                          const gchar             *iface,
                                                          GError                 **error);
+GLIB_AVAILABLE_IN_2_56
+gboolean               g_socket_join_multicast_group_ssm    (GSocket                 *socket,
+                                                             GInetAddress            *group,
+                                                             GInetAddress            *source_specific,
+                                                             const gchar             *iface,
+                                                             GError                 **error);
+GLIB_AVAILABLE_IN_2_56
+gboolean               g_socket_leave_multicast_group_ssm   (GSocket                 *socket,
+                                                             GInetAddress            *group,
+                                                             GInetAddress            *source_specific,
+                                                             const gchar             *iface,
+                                                             GError                 **error);
 GLIB_AVAILABLE_IN_ALL
 gboolean               g_socket_connect                 (GSocket                 *socket,
                                                         GSocketAddress          *address,
diff --git a/gio/meson.build b/gio/meson.build
index 87f0861..4a10d49 100644
--- a/gio/meson.build
+++ b/gio/meson.build
@@ -93,6 +93,18 @@ if host_system != 'windows'
                  name : 'struct ip_mreqn')
     glib_conf.set('HAVE_IP_MREQN', '/**/')
   endif
+
+  if cc.compiles('''#include <sys/ioctl.h>
+                    #include <net/if.h>
+                    int main (int argc, char ** argv) {
+                      struct ifreq ifr;
+                      ioctl(0, SIOCGIFADDR, &ifr);
+                      return 0;
+                    }''',
+                 name : 'ioctl with request SIOCGIFADDR')
+    glib_conf.set('HAVE_SIOCGIFADDR', '/**/')
+  endif
+
 endif
 
 network_args_string = ''


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]