diff --git a/config.h.meson b/config.h.meson index 6f0b7e429..c67e1792f 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 c0825d7f0..9cd151075 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 + #include + ]], + [[ + 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], [ # 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 c21d71ee3..484ef6242 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -2095,6 +2095,8 @@ g_socket_get_credentials 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 2d4669be0..c4147fb76 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -44,6 +44,10 @@ # include #endif +#ifdef HAVE_SIOCGIFADDR +#include +#endif + #ifdef HAVE_SYS_FILIO_H # include #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 613c8dd92..a65cbc22f 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 87f0861d5..4a10d49d7 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 + #include + 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 = ''