diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 8f27996cf..110e7d069 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1822,6 +1822,13 @@ g_socket_get_remote_address g_socket_get_socket_type g_socket_speaks_ipv4 g_socket_get_credentials + +g_socket_join_multicast_group +g_socket_leave_multicast_group +g_socket_get_multicast_loopback +g_socket_set_multicast_loopback +g_socket_get_multicast_ttl +g_socket_set_multicast_ttl GSocketClass G_IS_SOCKET diff --git a/gio/gio.symbols b/gio/gio.symbols index dcf9ec785..5f71700d5 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -956,11 +956,15 @@ g_socket_get_timeout g_socket_get_keepalive g_socket_get_listen_backlog g_socket_get_local_address +g_socket_get_multicast_loopback +g_socket_get_multicast_ttl g_socket_get_protocol g_socket_get_remote_address g_socket_get_socket_type g_socket_is_closed g_socket_is_connected +g_socket_join_multicast_group +g_socket_leave_multicast_group g_socket_listen g_socket_new g_socket_new_from_fd @@ -976,6 +980,8 @@ g_socket_set_blocking g_socket_set_timeout g_socket_set_keepalive g_socket_set_listen_backlog +g_socket_set_multicast_loopback +g_socket_set_multicast_ttl g_socket_speaks_ipv4 g_socket_get_credentials g_socket_control_message_get_type diff --git a/gio/gsocket.c b/gio/gsocket.c index 9996a63ed..416ac77db 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -137,7 +137,9 @@ enum PROP_KEEPALIVE, PROP_LOCAL_ADDRESS, PROP_REMOTE_ADDRESS, - PROP_TIMEOUT + PROP_TIMEOUT, + PROP_MULTICAST_LOOPBACK, + PROP_MULTICAST_TTL }; struct _GSocketPrivate @@ -613,6 +615,14 @@ g_socket_get_property (GObject *object, g_value_set_uint (value, socket->priv->timeout); break; + case PROP_MULTICAST_LOOPBACK: + g_value_set_boolean (value, g_socket_get_multicast_loopback (socket)); + break; + + case PROP_MULTICAST_TTL: + g_value_set_uint (value, g_socket_get_multicast_ttl (socket)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -660,6 +670,14 @@ g_socket_set_property (GObject *object, g_socket_set_timeout (socket, g_value_get_uint (value)); break; + case PROP_MULTICAST_LOOPBACK: + g_socket_set_multicast_loopback (socket, g_value_get_boolean (value)); + break; + + case PROP_MULTICAST_TTL: + g_socket_set_multicast_ttl (socket, g_value_get_uint (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -817,6 +835,36 @@ g_socket_class_init (GSocketClass *klass) 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GSocket:multicast-loopback: + * + * Whether outgoing multicast packets loop back to the local host. + * + * Since: 2.32 + */ + g_object_class_install_property (gobject_class, PROP_MULTICAST_LOOPBACK, + g_param_spec_boolean ("multicast-loopback", + P_("Multicast loopback"), + P_("Whether outgoing multicast packets loop back to the local host"), + TRUE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * GSocket:multicast-ttl: + * + * Time-to-live out outgoing multicast packets + * + * Since: 2.32 + */ + g_object_class_install_property (gobject_class, PROP_MULTICAST_TTL, + g_param_spec_uint ("multicast-ttl", + P_("Multicast TTL"), + P_("Time-to-live of outgoing multicast packets"), + 0, G_MAXUINT, 1, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); } static void @@ -1163,6 +1211,191 @@ g_socket_set_timeout (GSocket *socket, } } +/** + * g_socket_get_multicast_loopback: + * @socket: a #GSocket. + * + * Gets the multicast loopback setting on @socket; if %TRUE (the + * default), outgoing multicast packets will be looped back to + * multicast listeners on the same host. + * + * Returns: the multicast loopback setting on @socket + * + * Since: 2.32 + */ +gboolean +g_socket_get_multicast_loopback (GSocket *socket) +{ + int result; + guint value = 0, optlen; + + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + if (socket->priv->family == G_SOCKET_FAMILY_IPV4) + { + optlen = sizeof (guchar); + result = getsockopt (socket->priv->fd, IPPROTO_IP, IP_MULTICAST_LOOP, + &value, &optlen); + } + else if (socket->priv->family == G_SOCKET_FAMILY_IPV6) + { + optlen = sizeof (guint); + result = getsockopt (socket->priv->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &value, &optlen); + } + else + g_return_val_if_reached (FALSE); + + if (result < 0) + { + int errsv = get_socket_errno (); + g_warning ("error getting multicast loopback: %s", socket_strerror (errsv)); + return FALSE; + } + + return !!value; +} + +/** + * g_socket_set_multicast_loopback: + * @socket: a #GSocket. + * @loopback: whether @socket should receive messages sent to its + * multicast groups from the local host + * + * Sets whether outgoing multicast packets will be received by sockets + * listening on that multicast address on the same host. This is %TRUE + * by default. + * + * Since: 2.32 + */ +void +g_socket_set_multicast_loopback (GSocket *socket, + gboolean loopback) +{ + int result; + + g_return_if_fail (G_IS_SOCKET (socket)); + + loopback = !!loopback; + + if (socket->priv->family == G_SOCKET_FAMILY_IPV4) + { + guchar value = (guchar)loopback; + + result = setsockopt (socket->priv->fd, IPPROTO_IP, IP_MULTICAST_LOOP, + &value, sizeof (value)); + } + else if (socket->priv->family == G_SOCKET_FAMILY_IPV6) + { + guint value = (guint)loopback; + + result = setsockopt (socket->priv->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &value, sizeof (value)); + } + else + g_return_if_reached (); + + if (result < 0) + { + int errsv = get_socket_errno (); + g_warning ("error setting multicast loopback: %s", socket_strerror (errsv)); + return; + } + + g_object_notify (G_OBJECT (socket), "multicast-loopback"); +} + +/** + * g_socket_get_multicast_ttl: + * @socket: a #GSocket. + * + * Gets the multicast time-to-live setting on @socket; see + * g_socket_set_multicast_ttl() for more details. + * + * Returns: the multicast time-to-live setting on @socket + * + * Since: 2.32 + */ +guint +g_socket_get_multicast_ttl (GSocket *socket) +{ + int result; + guint value, optlen; + + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + if (socket->priv->family == G_SOCKET_FAMILY_IPV4) + { + guchar optval; + + optlen = sizeof (optval); + result = getsockopt (socket->priv->fd, IPPROTO_IP, IP_MULTICAST_TTL, + &optval, &optlen); + value = optval; + } + else if (socket->priv->family == G_SOCKET_FAMILY_IPV6) + { + optlen = sizeof (value); + result = getsockopt (socket->priv->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &value, &optlen); + } + else + g_return_val_if_reached (FALSE); + + if (result < 0) + { + int errsv = get_socket_errno (); + g_warning ("error getting multicast ttl: %s", socket_strerror (errsv)); + return FALSE; + } + + return value; +} + +/** + * g_socket_set_multicast_ttl: + * @socket: a #GSocket. + * @ttl: the time-to-live value for all multicast datagrams on @socket + * + * Sets the time-to-live for outgoing multicast datagrams on @socket. + * By default, this is 1, meaning that multicast packets will not leave + * the local network. + * + * Since: 2.32 + */ +void +g_socket_set_multicast_ttl (GSocket *socket, + guint ttl) +{ + int result; + + g_return_if_fail (G_IS_SOCKET (socket)); + + if (socket->priv->family == G_SOCKET_FAMILY_IPV4) + { + guchar optval = (guchar)ttl; + + result = setsockopt (socket->priv->fd, IPPROTO_IP, IP_MULTICAST_TTL, + &optval, sizeof (optval)); + } + else if (socket->priv->family == G_SOCKET_FAMILY_IPV6) + { + result = setsockopt (socket->priv->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &ttl, sizeof (ttl)); + } + else + g_return_if_reached (); + + if (result < 0) + { + int errsv = get_socket_errno (); + g_warning ("error setting multicast ttl: %s", socket_strerror (errsv)); + return; + } + + g_object_notify (G_OBJECT (socket), "multicast-ttl"); +} + /** * g_socket_get_family: * @socket: a #GSocket. @@ -1452,6 +1685,108 @@ g_socket_bind (GSocket *socket, return TRUE; } +static gboolean +g_socket_multicast_group_operation (GSocket *socket, + GInetAddress *group, + gboolean join_group, + GError **error) +{ + const guint8 *native_addr; + gint optname, 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 (g_inet_address_get_family (group) == socket->priv->family, FALSE); + + if (!check_socket (socket, error)) + return FALSE; + + native_addr = g_inet_address_to_bytes (group); + if (socket->priv->family == G_SOCKET_FAMILY_IPV4) + { + struct ip_mreq mc_req; + + memcpy (&mc_req.imr_multiaddr, native_addr, sizeof (struct in_addr)); + mc_req.imr_interface.s_addr = g_htonl (INADDR_ANY); + + optname = join_group ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; + result = setsockopt (socket->priv->fd, IPPROTO_IP, optname, + &mc_req, sizeof (mc_req)); + } + else if (socket->priv->family == G_SOCKET_FAMILY_IPV6) + { + struct ipv6_mreq mc_req_ipv6; + + memcpy (&mc_req_ipv6.ipv6mr_multiaddr, native_addr, sizeof (struct in6_addr)); + mc_req_ipv6.ipv6mr_interface = 0; + + optname = join_group ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP; + result = setsockopt (socket->priv->fd, IPPROTO_IPV6, optname, + &mc_req_ipv6, sizeof (mc_req_ipv6)); + } + else + 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: + * @socket: a #GSocket. + * @group: a #GInetAddress specifying the group address to join. + * @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(). + * + * Returns: %TRUE on success, %FALSE on error. + * + * Since: 2.32 + */ +gboolean +g_socket_join_multicast_group (GSocket *socket, + GInetAddress *group, + GError **error) +{ + return g_socket_multicast_group_operation (socket, group, TRUE, error); +} + +/** + * g_socket_leave_multicast_group: + * @socket: a #GSocket. + * @group: a #GInetAddress specifying the group address to leave. + * @error: #GError for error reporting, or %NULL to ignore. + * + * Removes @socket from the multicast group @group (while still + * allowing it to receive unicast messages). + * + * Returns: %TRUE on success, %FALSE on error. + * + * Since: 2.32 + */ +gboolean +g_socket_leave_multicast_group (GSocket *socket, + GInetAddress *group, + GError **error) +{ + return g_socket_multicast_group_operation (socket, group, FALSE, error); +} + /** * g_socket_speaks_ipv4: * @socket: a #GSocket diff --git a/gio/gsocket.h b/gio/gsocket.h index d17de3257..d83387515 100644 --- a/gio/gsocket.h +++ b/gio/gsocket.h @@ -100,17 +100,30 @@ void g_socket_set_listen_backlog (GSocket guint g_socket_get_timeout (GSocket *socket); void g_socket_set_timeout (GSocket *socket, guint timeout); +gboolean g_socket_get_multicast_loopback (GSocket *socket); +void g_socket_set_multicast_loopback (GSocket *socket, + gboolean loopback); +guint g_socket_get_multicast_ttl (GSocket *socket); +void g_socket_set_multicast_ttl (GSocket *socket, + guint ttl); gboolean g_socket_is_connected (GSocket *socket); gboolean g_socket_bind (GSocket *socket, GSocketAddress *address, gboolean allow_reuse, GError **error); +gboolean g_socket_join_multicast_group (GSocket *socket, + GInetAddress *group, + GError **error); +gboolean g_socket_leave_multicast_group (GSocket *socket, + GInetAddress *group, + GError **error); gboolean g_socket_connect (GSocket *socket, GSocketAddress *address, GCancellable *cancellable, GError **error); gboolean g_socket_check_connect_result (GSocket *socket, GError **error); + GIOCondition g_socket_condition_check (GSocket *socket, GIOCondition condition); gboolean g_socket_condition_wait (GSocket *socket,