/* 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.
 *
 *  <table>
 *    <title>TTL and scope</title>
 *    <tgroup cols="2" align="left">
 *    <thead>
 *      <row>
 *        <entry>TTL</entry>
 *        <entry>Scope</entry>
 *      </row>
 *    </thead>
 *    <tbody>
 *      <row>
 *        <entry>0</entry>
 *        <entry>node local</entry>
 *      </row>
 *      <row>
 *        <entry>1</entry>
 *        <entry>link local</entry>
 *      </row>
 *      <row>
 *        <entry>2-32</entry>
 *        <entry>site local</entry>
 *      </row>
 *      <row>
 *        <entry>33-64</entry>
 *        <entry>region local</entry>
 *      </row>
 *      <row>
 *        <entry>65-128</entry>
 *        <entry>continent local</entry>
 *      </row>
 *      <row>
 *        <entry>129-255</entry>
 *        <entry>unrestricted (global)</entry>
 *      </row>
 *    </tbody>
 *    </tgroup>
 *  </table>
 *
 *  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);
}



syntax highlighted by Code2HTML, v. 0.9.1