/* GNet - Networking library * Copyright (C) 2000, 2002-3 David Helder * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "gnet-private.h" #include "mcast.h" /** * gnet_mcast_socket_new * * Creates a #GMcastSocket bound to all interfaces and an arbitrary * port. * * Returns: a new #GMcastSocket; NULL on error. * **/ GMcastSocket* gnet_mcast_socket_new (void) { return gnet_mcast_socket_new_full (NULL, 0); } /** * gnet_mcast_socket_new_with_port * @port: port to bind to * * Creates a #GMcastSocket bound to all interfaces and port @port. * If @port is 0, an arbitrary port will be used. Use this * constructor if you know the port of the group you will join. Most * applications will use this constructor. * * Returns: a new #GMcastSocket; NULL on error. * **/ GMcastSocket* gnet_mcast_socket_new_with_port (gint port) { return gnet_mcast_socket_new_full (NULL, port); } /** * gnet_mcast_socket_new_full * @iface: interface to bind to (NULL for all interfaces) * @port: port to bind to (0 for an arbitrary port) * * Creates a #GMcastSocket bound to interface @iface and port @port. * If @iface is NULL, all interfaces will be used. If @port is 0, an * arbitrary port will be used. To receive packets from this group, * call gnet_mcast_socket_join_group() next. Loopback is disabled by * default. * * Returns: a new #GMcastSocket; NULL on error. * **/ GMcastSocket* gnet_mcast_socket_new_full (const GInetAddr* iface, gint port) { int sockfd; struct sockaddr_storage sa; GMcastSocket* ms; const int on = 1; /* Create sockfd and address */ sockfd = gnet_private_create_listen_socket (SOCK_DGRAM, iface, port, &sa); if (!GNET_IS_SOCKET_VALID(sockfd)) { g_warning ("socket() failed"); return NULL; } /* Set socket option to share the UDP port */ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*) &on, sizeof(on)) != 0) g_warning("Can't reuse mcast socket\n"); /* Bind to the socket to some local address and port */ if (bind(sockfd, &GNET_SOCKADDR_SA(sa), GNET_SOCKADDR_LEN(sa)) != 0) { GNET_CLOSE_SOCKET (sockfd); return NULL; } /* Create socket */ ms = g_new0(GMcastSocket, 1); ms->sockfd = sockfd; ms->sa = sa; ms->ref_count = 1; gnet_mcast_socket_set_loopback (ms, FALSE); return ms; } /** * gnet_mcast_socket_delete: * @socket: a #GMcastSocket * * Deletes a #GMcastSocket. * **/ void gnet_mcast_socket_delete (GMcastSocket* socket) { if (socket != NULL) gnet_mcast_socket_unref(socket); } /** * gnet_mcast_socket_ref * @socket: a #GMcastSocket * * Adds a reference to a #GMcastSocket. * **/ void gnet_mcast_socket_ref (GMcastSocket* socket) { g_return_if_fail (socket != NULL); socket->ref_count++; } /** * gnet_mcast_socket_unref * @socket: a #GMcastSocket * * Removes a reference from a #GMcastSocket. A #GMcastSocket is * deleted when the reference count reaches 0. * **/ void gnet_mcast_socket_unref (GMcastSocket* socket) { g_return_if_fail (socket != NULL); socket->ref_count--; if (socket->ref_count == 0) { GNET_CLOSE_SOCKET(socket->sockfd);/* Don't care if this fails... */ if (socket->iochannel) g_io_channel_unref (socket->iochannel); g_free (socket); } } /** * gnet_mcast_socket_get_io_channel: * @socket: a #GMcastSocket * * Gets the #GIOChannel of a #GMcastSocket. * * Use the channel with g_io_add_watch() to check if the socket is * readable or writable. If the channel is readable, call * gnet_mcast_socket_receive() to receive a packet. If the channel * is writable, call gnet_mcast_socket_send() to send a packet. This * is not a normal giochannel - do not read from or write to it. * * Every #GMcastSocket has one and only one #GIOChannel. If you ref * the channel, then you must unref it eventually. Do not close the * channel. The channel is closed by GNet when the socket is * deleted. * * Returns: a #GIOChannel. * **/ GIOChannel* gnet_mcast_socket_get_io_channel (GMcastSocket* socket) { return gnet_udp_socket_get_io_channel((GUdpSocket*) socket); } /** * gnet_mcast_socket_get_local_inetaddr * @socket: a #GMcastSocket * * Gets the local host's address from a #GMcastSocket. * * Returns: a #GInetAddr. * **/ GInetAddr* gnet_mcast_socket_get_local_inetaddr (const GMcastSocket* socket) { return gnet_udp_socket_get_local_inetaddr((GUdpSocket*) socket); } /** * gnet_mcast_socket_join_group * @socket: a #GMcastSocket * @inetaddr: address of the group * * Joins a multicast group. Join only one group per socket. * * Returns: 0 on success. * **/ gint gnet_mcast_socket_join_group (GMcastSocket* socket, const GInetAddr* inetaddr) { gint rv = -1; if (GNET_INETADDR_FAMILY(inetaddr) == AF_INET) { struct ip_mreq mreq; /* Create the multicast request structure */ memcpy(&mreq.imr_multiaddr, GNET_INETADDR_ADDRP(inetaddr), sizeof(mreq.imr_multiaddr)); mreq.imr_interface.s_addr = g_htonl(INADDR_ANY); /* Join the group */ rv = setsockopt(socket->sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mreq, sizeof(mreq)); } #ifdef HAVE_IPV6 else if (GNET_INETADDR_FAMILY(inetaddr) == AF_INET6) { struct ipv6_mreq mreq; /* Create the multicast request structure */ memcpy(&mreq.ipv6mr_multiaddr, GNET_INETADDR_ADDRP(inetaddr), sizeof(mreq.ipv6mr_multiaddr)); mreq.ipv6mr_interface = 0; /* Join the group */ rv = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void*) &mreq, sizeof(mreq)); } #endif else g_assert_not_reached (); return rv; } /** * gnet_mcast_socket_leave_group * @socket: a #GMcastSocket * @inetaddr: address of the group * * Leaves a mulitcast group. * * Returns: 0 on success. * **/ gint gnet_mcast_socket_leave_group (GMcastSocket* socket, const GInetAddr* inetaddr) { gint rv = -1; if (GNET_INETADDR_FAMILY(inetaddr) == AF_INET) { struct ip_mreq mreq; /* Create the multicast request structure */ memcpy(&mreq.imr_multiaddr, GNET_INETADDR_ADDRP(inetaddr), sizeof(mreq.imr_multiaddr)); mreq.imr_interface.s_addr = g_htonl(INADDR_ANY); /* Join the group */ rv = setsockopt(socket->sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &mreq, sizeof(mreq)); } #ifdef HAVE_IPV6 else if (GNET_INETADDR_FAMILY(inetaddr) == AF_INET6) { struct ipv6_mreq mreq; /* Create the multicast request structure */ memcpy(&mreq.ipv6mr_multiaddr, GNET_INETADDR_ADDRP(inetaddr), sizeof(mreq.ipv6mr_multiaddr)); mreq.ipv6mr_interface = 0; /* Join the group */ rv = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (void*) &mreq, sizeof(mreq)); } #endif else g_assert_not_reached (); return rv; } /** * gnet_mcast_socket_get_ttl * @socket: a #GMcastSocket * * Gets the multicast time-to-live (TTL) of a #GMcastSocket. All IP * multicast packets have a TTL field. This field is decremented by * a router before it forwards the packet. If the TTL reaches zero, * the packet is discarded. The default value is sufficient for most * applications. * * The table below shows the scope for a given TTL. The scope is * only an estimate. * * * TTL and scope * * * * TTL * Scope * * * * * 0 * node local * * * 1 * link local * * * 2-32 * site local * * * 33-64 * region local * * * 65-128 * continent local * * * 129-255 * unrestricted (global) * * * *
* * Returns: the TTL (an integer between 0 and 255), -1 if the kernel * default is being used, or an integer less than -1 on error. * **/ gint gnet_mcast_socket_get_ttl (const GMcastSocket* socket) { guchar ttl; socklen_t ttl_size; int rv = -1; ttl_size = sizeof(ttl); /* Get the IPv4 TTL if the socket is bound to an IPv4 address */ if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET) { rv = getsockopt(socket->sockfd, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &ttl, &ttl_size); } /* Get the IPv6 TTL if the socket is bound to an IPv6 address */ #ifdef HAVE_IPV6 else if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6) { rv = getsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void*) &ttl, &ttl_size); } #endif else g_assert_not_reached (); if (rv == -1) return -2; return ttl; } /** * gnet_mcast_socket_set_ttl * @socket: a #GMcastSocket * @ttl: value to set TTL to * * Sets the time-to-live (TTL) default of a #GMcastSocket. Set the TTL * to -1 to use the kernel default. The default value is sufficient * for most applications. * * Returns: 0 if successful. * **/ gint gnet_mcast_socket_set_ttl (GMcastSocket* socket, gint ttl) { guchar ttlb; int rv1, rv2; #ifdef HAVE_IPV6 GIPv6Policy policy; #endif rv1 = -1; rv2 = -1; /* If the bind address is anything IPv4 *or* the bind address is 0::0 IPv6 and IPv6 policy allows IPv4, set IP_TTL. In the latter case, if we bind to 0::0 and the host is dual-stacked, then all IPv4 interfaces will be bound to also. */ if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET #ifdef HAVE_IPV6 || (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&GNET_SOCKADDR_SA6(socket->sa).sin6_addr) && ((policy = gnet_ipv6_get_policy()) == GIPV6_POLICY_IPV4_THEN_IPV6 || policy == GIPV6_POLICY_IPV6_THEN_IPV4)) #endif ) { ttlb = ttl; rv1 = setsockopt(socket->sockfd, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &ttlb, sizeof(ttlb)); } /* If the bind address is IPv6, set IPV6_UNICAST_HOPS */ #ifdef HAVE_IPV6 if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6) { ttlb = ttl; rv2 = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void*) &ttlb, sizeof(ttlb)); } #endif if (rv1 == -1 && rv2 == -1) return -1; return 0; } /** * gnet_mcast_socket_is_loopback * @socket: a #GMcastSocket * * Checks if a #GMcastSocket has loopback enabled. If loopback is * enabled, all packets sent by the host will also be received by the * host. Loopback is disabled by default. * * Returns: 0 if loopback is disabled, 1 if enabled, and -1 on error. * **/ gint gnet_mcast_socket_is_loopback (const GMcastSocket* socket) { socklen_t flag_size; int rv = -1; gint is_loopback = 0; /* Get IPv4 loopback if it's bound to a IPv4 address */ if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET) { guchar flag; flag_size = sizeof (flag); rv = getsockopt(socket->sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, &flag_size); if (flag) is_loopback = 1; } /* Otherwise, get IPv6 loopback */ #ifdef HAVE_IPV6 else if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6) { guint flag; flag_size = sizeof (flag); rv = getsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &flag, &flag_size); if (flag) is_loopback = 1; } #endif else g_assert_not_reached(); if (rv == -1) return -1; return is_loopback; } /** * gnet_mcast_socket_set_loopback * @socket: a #GMcastSocket * @enable: should loopback be enabled? * * Enables (or disables) loopback on a #GMcastSocket. Loopback is * disabled by default. * * Returns: 0 if successful. * **/ gint gnet_mcast_socket_set_loopback (GMcastSocket* socket, gboolean enable) { int rv1, rv2; #ifdef HAVE_IPV6 GIPv6Policy policy; #endif rv1 = -1; rv2 = -1; /* Set IPv4 loopback. (As in set_ttl().) */ if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET #ifdef HAVE_IPV6 || (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&GNET_SOCKADDR_SA6(socket->sa).sin6_addr) && ((policy = gnet_ipv6_get_policy()) == GIPV6_POLICY_IPV4_THEN_IPV6 || policy == GIPV6_POLICY_IPV6_THEN_IPV4)) #endif ) { guchar flag; flag = (guchar) enable; rv1 = setsockopt(socket->sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, sizeof(flag)); } /* Set IPv6 loopback */ #ifdef HAVE_IPV6 if (GNET_SOCKADDR_FAMILY(socket->sa) == AF_INET6) { guint flag; flag = (guint) enable; rv2 = setsockopt(socket->sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &flag, sizeof(flag)); } #endif if (rv1 == -1 && rv2 == -1) return -1; return 0; } /** * gnet_mcast_socket_send * @socket: a #GMcastSocket * @buffer: buffer to send * @length: length of buffer * @dst: destination address * * Sends data to a host using a #GMcastSocket. * * Returns: 0 if successful. * **/ gint gnet_mcast_socket_send (GMcastSocket* socket, const gchar* buffer, gint length, const GInetAddr* dst) { return gnet_udp_socket_send((GUdpSocket*) socket, buffer, length, dst); } /** * gnet_mcast_socket_receive * @socket: a #GMcastSocket * @buffer: buffer to write to * @length: length of @buffer * @src: pointer to source address (optional) * * Receives data using a #GMcastSocket. If @src is set, the source * address is stored in the location @src points to. The address is * caller owned. * * Returns: the number of bytes received, -1 if unsuccessful. * **/ gint gnet_mcast_socket_receive (GMcastSocket* socket, gchar* buffer, gint length, GInetAddr** src) { return gnet_udp_socket_receive((GUdpSocket*) socket, buffer, length, src); } /** * gnet_mcast_socket_has_packet: * @socket: a #GMcastSocket * * Tests if a #GMcastSocket has a packet waiting to be received. * * Returns: TRUE if there is packet waiting, FALSE otherwise. * **/ gboolean gnet_mcast_socket_has_packet (const GMcastSocket* socket) { return gnet_udp_socket_has_packet((const GUdpSocket*) socket); }