gio: Fix multicast iface selection on macOS

ip_mreqn.imr_ifindex is not used correctly by the XNU kernel, and
causes us to bind to the default interface; so fallback to ip_mreq
and set the iface source address (not SSM).

Fixes https://gitlab.gnome.org/GNOME/glib/-/issues/3489
This commit is contained in:
Nirbheek Chauhan 2024-10-07 14:02:02 +00:00 committed by Philip Withnall
parent c94cbf2368
commit 469084c45b

View File

@ -2402,6 +2402,42 @@ g_socket_w32_get_adapter_ipv4_addr (const gchar *name_or_ip)
return ip_result; return ip_result;
} }
#elif defined(HAVE_SIOCGIFADDR)
static gulong
g_socket_get_adapter_ipv4_addr (GSocket *socket,
const char *iface,
GError **error)
{
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 ULONG_MAX;
}
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 ULONG_MAX;
}
iface_addr = (struct sockaddr_in *) &ifr.ifr_addr;
return iface_addr->sin_addr.s_addr;
}
#endif #endif
static gboolean static gboolean
@ -2418,6 +2454,7 @@ g_socket_multicast_group_operation (GSocket *socket,
g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); 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 (socket->priv->type == G_SOCKET_TYPE_DATAGRAM, FALSE);
g_return_val_if_fail (G_IS_INET_ADDRESS (group), FALSE); g_return_val_if_fail (G_IS_INET_ADDRESS (group), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!check_socket (socket, error)) if (!check_socket (socket, error))
return FALSE; return FALSE;
@ -2425,7 +2462,7 @@ g_socket_multicast_group_operation (GSocket *socket,
native_addr = g_inet_address_to_bytes (group); native_addr = g_inet_address_to_bytes (group);
if (g_inet_address_get_family (group) == G_SOCKET_FAMILY_IPV4) if (g_inet_address_get_family (group) == G_SOCKET_FAMILY_IPV4)
{ {
#ifdef HAVE_IP_MREQN #if defined(HAVE_IP_MREQN) && !defined(__APPLE__)
struct ip_mreqn mc_req; struct ip_mreqn mc_req;
#else #else
struct ip_mreq mc_req; struct ip_mreq mc_req;
@ -2434,7 +2471,11 @@ g_socket_multicast_group_operation (GSocket *socket,
memset (&mc_req, 0, sizeof (mc_req)); memset (&mc_req, 0, sizeof (mc_req));
memcpy (&mc_req.imr_multiaddr, native_addr, sizeof (struct in_addr)); memcpy (&mc_req.imr_multiaddr, native_addr, sizeof (struct in_addr));
#ifdef HAVE_IP_MREQN /* mc_req.imr_ifindex is not used correctly by the XNU kernel, and
* causes us to bind to the default interface; so fallback to ip_mreq
* and set the iface source address (not SSM).
* See: https://gitlab.gnome.org/GNOME/glib/-/issues/3489 */
#if defined(HAVE_IP_MREQN) && !defined(__APPLE__)
if (iface) if (iface)
mc_req.imr_ifindex = if_nametoindex (iface); mc_req.imr_ifindex = if_nametoindex (iface);
else else
@ -2444,6 +2485,22 @@ g_socket_multicast_group_operation (GSocket *socket,
mc_req.imr_interface.s_addr = g_socket_w32_get_adapter_ipv4_addr (iface); mc_req.imr_interface.s_addr = g_socket_w32_get_adapter_ipv4_addr (iface);
else else
mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY); mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY);
#elif defined(HAVE_SIOCGIFADDR)
if (iface)
{
GError *local_error = NULL;
mc_req.imr_interface.s_addr = g_socket_get_adapter_ipv4_addr (socket, iface, &local_error);
if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
}
else
{
mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY);
}
#else #else
mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY); mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY);
#endif #endif
@ -2642,36 +2699,15 @@ g_socket_multicast_group_operation_ssm (GSocket *socket,
{ {
#if defined(G_OS_WIN32) #if defined(G_OS_WIN32)
S_ADDR_FIELD(mc_req_src) = g_socket_w32_get_adapter_ipv4_addr (iface); S_ADDR_FIELD(mc_req_src) = g_socket_w32_get_adapter_ipv4_addr (iface);
#elif defined (HAVE_SIOCGIFADDR) #elif defined(HAVE_SIOCGIFADDR)
int ret; GError *local_error = NULL;
struct ifreq ifr;
struct sockaddr_in *iface_addr;
size_t if_name_len = strlen (iface);
memset (&ifr, 0, sizeof (ifr)); S_ADDR_FIELD(mc_req_src) = g_socket_get_adapter_ipv4_addr (socket, iface, &local_error);
if (local_error != NULL)
if (if_name_len >= sizeof (ifr.ifr_name))
{ {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG, g_propagate_error (error, g_steal_pointer (&local_error));
_("Interface name too long"));
return FALSE; 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;
S_ADDR_FIELD(mc_req_src) = iface_addr->sin_addr.s_addr;
#endif /* defined(G_OS_WIN32) && defined (HAVE_IF_NAMETOINDEX) */ #endif /* defined(G_OS_WIN32) && defined (HAVE_IF_NAMETOINDEX) */
} }