/* VMPlainSocketImpl.c - Native methods for PlainSocketImpl class
   Copyright (C) 2005, 2006 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
 
GNU Classpath 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
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#include <config-int.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
#include <jni.h>
#include <jcl.h>

#include "cpnative.h"
#include "cpnet.h"
#include "cpio.h"
#include "javanet.h"

#include "gnu_java_net_VMPlainSocketImpl.h"

#define THROW_NO_NETWORK(env) JCL_ThrowException (env, "java/lang/InternalError", "this platform not configured for network support")
#define THROW_NO_IPV6(env)    JCL_ThrowException (env, "java/lang/InternalError", "IPv6 support not available")

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    bind
 * Signature: (I[BI)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_bind (JNIEnv *env,
                                          jclass clazz __attribute__((unused)),
                                          jint fd, jbyteArray addr, jint port)
{
  struct sockaddr_in sockaddr;
  jbyte *elems = NULL;
  int ret;

  if (addr != NULL)
    elems = (*env)->GetByteArrayElements (env, addr, NULL);

  memset(&sockaddr, 0, sizeof (struct sockaddr_in));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_port = htons (port);
  /* addr is already in network byte order. */
  if (elems != NULL)
    sockaddr.sin_addr.s_addr = *((uint32_t *) elems);
  else
    sockaddr.sin_addr.s_addr = INADDR_ANY;

  /* bind(2) from BSD says bind will never return EINTR */
  /* bind is not a blocking system call */
  ret = bind (fd, (struct sockaddr *) &sockaddr, sizeof (struct sockaddr_in));

  if (elems != NULL)
    (*env)->ReleaseByteArrayElements (env, addr, elems, JNI_ABORT);

#ifdef _WIN32_WCE
  if (SOCKET_ERROR == ret)
  {
    DWORD dwError = WSAGetLastError();
    if (WSAEADDRINUSE == dwError)
    {
      (*env)->ThrowNew(env, (*env)->FindClass(env, BIND_EXCEPTION), "Address already in use");
    }
    else
    {
      JCL_ThrowExceptionBasedOnErrorCode(env, BIND_EXCEPTION, WSAGetLastError());
    }
  }
#else
  if (-1 == ret)
    JCL_ThrowException (env, BIND_EXCEPTION, strerror (errno));
#endif

#if !defined(_WIN32_WCE)
  cpio_closeOnExec(ret);
#endif
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    bind6
 * Signature: (I[BI)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_bind6 (JNIEnv *env,
                                           jclass c __attribute__((unused)),
                                           jint fd, jbyteArray addr, jint port)
{
#ifdef HAVE_INET6
  struct sockaddr_in6 sockaddr;
  jbyte *elems;
  int ret;

  elems = (*env)->GetByteArrayElements (env, addr, NULL);

  memset (&sockaddr, 0, sizeof (struct sockaddr_in6));
  sockaddr.sin6_family = AF_INET6;
  sockaddr.sin6_port = htons (port);
  memcpy (&sockaddr.sin6_addr.s6_addr, elems, 16);

  /* bind(2) from BSD says bind will never return EINTR */
  /* bind is not a blocking system call */
  ret = bind (fd, (struct sockaddr *) &sockaddr,
              sizeof (struct sockaddr_in6));

  (*env)->ReleaseByteArrayElements (env, addr, elems, JNI_ABORT);

  if (-1 == ret)
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#else
  THROW_NO_IPV6(env);
#endif
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    listen
 * Signature: (II)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_listen (JNIEnv *env,
                                            jclass c __attribute__((unused)),
                                            jint fd, jint backlog)
{
  int ret;

  /* listen(2) says that this call will never return EINTR */
  /* listen is not a blocking system call */
  if ((ret = listen (fd, backlog)) == -1)
#if _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif
}


/* These constants are also defined in java/net/SocketOptions.java.
 * Except for CPNET_IP_TTL which is defined in 
 * vm/reference/gnu/java/net/VMPlainSocketImpl.java .
 */
enum java_sockopt {
  CPNET_SO_KEEPALIVE = 0x8,
  CPNET_SO_LINGER = 0x80,
  CPNET_SO_TIMEOUT = 0x1006,
  CPNET_SO_BINDADDR = 0x0F,
  CPNET_SO_SNDBUF = 0x1001,
  CPNET_SO_RCVBUF = 0x1002,
  CPNET_SO_REUSEADDR = 0x04,
  CPNET_SO_BROADCAST = 0x20,
  CPNET_SO_OOBINLINE = 0x1003,
  CPNET_TCP_NODELAY = 0x01,
  CPNET_IP_MULTICAST_IF = 0x10,
  CPNET_IP_MULTICAST_IF2 = 0x1F,
  CPNET_IP_MULTICAST_LOOP = 0x12,
  CPNET_IP_TOS = 0x03,
  CPNET_IP_TTL = 0x1E61
};


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    setOption
 * Signature: (III)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_setOption (JNIEnv *env,
                                               jclass c __attribute__((unused)),
                                               jint fd, jint option, jint value)
{
  enum java_sockopt joption = (enum java_sockopt) option;
  int optname = -1;
  int level = SOL_SOCKET;
  const int _value = value;
  struct linger _linger;
  struct timeval _timeo;
  void *optval = (void *) &_value;
  socklen_t optlen = sizeof (int);
  
  switch (joption)
    {
    case CPNET_IP_MULTICAST_LOOP:
      level = IPPROTO_IP;
      optname = IP_MULTICAST_LOOP;
      break;

    case CPNET_SO_KEEPALIVE:
      optname = SO_KEEPALIVE;
      break;

    case CPNET_SO_LINGER:
      optname = SO_LINGER;
      if (_value == -1)
        _linger.l_onoff = 0;
      else
        _linger.l_onoff = 1;
      _linger.l_linger = _value;
      optval = &_linger;
      optlen = sizeof (struct linger);
      break;

    case CPNET_SO_TIMEOUT:
#ifdef _WIN32_WCE
		// Windows MobileのWinSockではSO_RCVTIMEOはサポートされていない
		// （setsockopt()はエラーを返す）
		cpnet_setSocketTimeout(env, fd, value);
		return;
#else
      optname = SO_RCVTIMEO;
      _timeo.tv_sec = value / 1000;
      _timeo.tv_usec = (value % 1000) * 1000;
      optval = &_timeo;
      optlen = sizeof (struct timeval);
      break;
#endif

    case CPNET_SO_SNDBUF:
      optname = SO_SNDBUF;
      break;

    case CPNET_SO_RCVBUF:
      optname = SO_RCVBUF;
      break;

    case CPNET_SO_REUSEADDR:
      optname = SO_REUSEADDR;
      break;

    case CPNET_SO_BROADCAST:
      optname = SO_BROADCAST;
      break;

    case CPNET_SO_OOBINLINE:
      optname = SO_OOBINLINE;
      break;

    case CPNET_TCP_NODELAY:
      level = IPPROTO_TCP;
      optname = TCP_NODELAY;
      break;

    case CPNET_IP_TOS:
      level = IPPROTO_IP;
      optname = IP_TOS;
      break;

    case CPNET_IP_TTL:
      level = IPPROTO_IP;
#ifdef _WIN32_WCE
	  optname = IP_MULTICAST_TTL;
#else
      optname = IP_TTL;
#endif /* _WIN32_WCE */
      break;

    case CPNET_SO_BINDADDR:
    case CPNET_IP_MULTICAST_IF:
    case CPNET_IP_MULTICAST_IF2:
      JCL_ThrowException (env, IO_EXCEPTION, "argument not a boolean or integer option");
      return;
    }

  if (setsockopt (fd, level, optname, (const void *) optval, optlen) == -1)
#if _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    getOption
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL
Java_gnu_java_net_VMPlainSocketImpl_getOption (JNIEnv *env,
                                               jclass c __attribute__((unused)),
                                               jint fd, jint option)
{
  enum java_sockopt joption = (enum java_sockopt) option;
  int optname = -1;
  int level = SOL_SOCKET;
  int value;
  struct linger linger;
  struct timeval timeo;
  void *optval = &value;
  socklen_t optlen = sizeof (int);

  switch (joption)
    {
    case CPNET_IP_MULTICAST_LOOP:
      level = IPPROTO_IP;
      optname = IP_MULTICAST_LOOP;
      break;

    case CPNET_SO_KEEPALIVE:
      optname = SO_KEEPALIVE;
      break;

    case CPNET_SO_LINGER:
      optname = SO_LINGER;
      optval = &linger;
      optlen = sizeof (struct linger);
      break;

    case CPNET_SO_TIMEOUT:
#ifdef _WIN32_WCE
		// Windows MobileのWinSockではSO_RCVTIMEOはサポートされていない
		// （getsockopt()はエラーを返す）
		do {
			int result;
			cpnet_getSocketTimeout(env, fd, &result);
			return result;
		} while (0);
#else
      optname = SO_RCVTIMEO;
      optval = &timeo;
      optlen = sizeof (struct timeval);
      break;
#endif
    case CPNET_SO_SNDBUF:
      optname = SO_SNDBUF;
      break;

    case CPNET_SO_RCVBUF:
      optname = SO_RCVBUF;
      break;

    case CPNET_SO_REUSEADDR:
      optname = SO_REUSEADDR;
      break;

    case CPNET_SO_BROADCAST:
      optname = SO_BROADCAST;
      break;

    case CPNET_SO_OOBINLINE:
      optname = SO_OOBINLINE;
      break;

    case CPNET_TCP_NODELAY:
      level = IPPROTO_TCP;
      optname = TCP_NODELAY;
      break;

    case CPNET_IP_TOS:
      level = IPPROTO_IP;
      optname = IP_TOS;
      break;

    case CPNET_IP_TTL:
      level = IPPROTO_IP;
#ifdef _WIN32_WCE
	  optname = IP_MULTICAST_TTL;
#else
      optname = IP_TTL;
#endif /* _WIN32_WCE */
      break;

    case CPNET_SO_BINDADDR:
    case CPNET_IP_MULTICAST_IF:
    case CPNET_IP_MULTICAST_IF2:
      JCL_ThrowException (env, IO_EXCEPTION, "argument not a boolean or integer option");
      return -1;
    }

  if (getsockopt (fd, level, optname, optval, &optlen) == -1)
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif

  /* Returns the linger value if it is enabled or -1 in case
   * it is disabled. This is how the Java API expects it.
   */
  if (joption == CPNET_SO_LINGER)
    return (linger.l_onoff) ? linger.l_linger : -1;
  if (joption == CPNET_SO_TIMEOUT)
    return (timeo.tv_sec * 1000) + (timeo.tv_usec / 1000);

  return value;
}

JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_setMulticastInterface (JNIEnv *env,
                                                           jclass c __attribute__((unused)),
                                                           jint fd,
                                                           jint optionId __attribute__((unused)),
                                                           jobject addr)
{
  int result;
  cpnet_address *cpaddr = _javanet_get_ip_netaddr (env, addr);

  if ((*env)->ExceptionOccurred (env))
    return;

#ifdef _WIN32_WCE
  {
	  struct sockaddr_in *netaddr = (struct sockaddr_in *) cpaddr->data;
	  result = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
							(const char*) &netaddr->sin_addr, sizeof(netaddr->sin_addr));
  }
#else
  result = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
                      (struct sockaddr *) cpaddr->data, cpaddr->len);
#endif
  cpnet_freeAddress (env, cpaddr);
  
  if (result == -1)
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, SOCKET_EXCEPTION, cpnative_getErrorString (errno));
#endif
}

JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_setMulticastInterface6 (JNIEnv *env,
                                                           jclass c __attribute__((unused)),
                                                           jint fd,
                                                           jint optionId __attribute__((unused)),
                                                           jstring ifname)
{
#ifndef _WIN32_WCE

#ifdef HAVE_SETSOCKOPT
#ifdef HAVE_INET6	
  int result;
  const char *str_ifname = JCL_jstring_to_cstring (env, ifname);
  unsigned int if_index;

  if ((*env)->ExceptionOccurred (env))
    {
      JCL_free_cstring(env, ifname, str_ifname);
      return;
    }

  if_index = if_nametoindex(str_ifname);
  if (!if_index)
    {
      JCL_free_cstring(env, ifname, str_ifname);
      JCL_ThrowException (env, SOCKET_EXCEPTION, "interface does not exist");
      return;
    }

  result = setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
                      (unsigned int *) &if_index, sizeof(if_index));

  JCL_free_cstring(env, ifname, str_ifname);
  
  if (result == -1)
    JCL_ThrowException (env, SOCKET_EXCEPTION, cpnative_getErrorString (errno));
#else
  (void) fd;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "IPv6 support not available");
#endif /* HAVE_INET6 */
#else
  (void) fd;
  THROW_NO_IPV6(env);
#endif /* HAVE_SETSOCKOPT */

#else
	JCL_ThrowException(env, SOCKET_EXCEPTION, "Windows Mobile does not have if_nametoindex()");
#endif
}

JNIEXPORT jobject JNICALL
Java_gnu_java_net_VMPlainSocketImpl_getMulticastInterface (JNIEnv *env,
                                                           jclass c __attribute__((unused)),
                                                           jint fd,
                                                           jint optionId __attribute__((unused)))
{
  jobject obj;
  cpnet_address *cpaddr;
  int result = cpnet_getMulticastIF (env, fd, &cpaddr);
 
  if (result != CPNATIVE_OK)
    {
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, SOCKET_EXCEPTION, WSAGetLastError());
#else
      JCL_ThrowException (env, SOCKET_EXCEPTION,
                          cpnative_getErrorString (result));
#endif
	  return (0);
    }

  obj = _javanet_create_inetaddress (env, cpaddr);
  cpnet_freeAddress (env, cpaddr);

  return obj;
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    shutdownInput
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_shutdownInput (JNIEnv *env,
                                                   jclass c __attribute__((unused)),
                                                   jint fd)
{
  if (shutdown (fd, SHUT_RD) == -1)
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    shutdownOutput
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_shutdownOutput (JNIEnv *env,
                                                    jclass c __attribute__((unused)),
                                                    jint fd)
{
  if (shutdown (fd, SHUT_WR) == -1)
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    sendUrgentData
 * Signature: (II)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_sendUrgentData (JNIEnv *env,
                                                    jclass c __attribute__((unused)),
                                                    jint fd, jint data)
{
  const char x = (char) data;

  if (send (fd, &x, 1, MSG_OOB) == -1)
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, IO_EXCEPTION, strerror (errno));
#endif
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    join
 * Signature: (I[B)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_join (JNIEnv *env,
                                          jclass clazz __attribute__((unused)),
                                          jint fd, jbyteArray addr)
{
#ifdef HAVE_SETSOCKOPT
  struct ip_mreq maddr;
  jbyte *addr_elems;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  maddr.imr_multiaddr.s_addr = * ((uint32_t *) addr_elems);
  maddr.imr_interface.s_addr = INADDR_ANY;

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                        &maddr, sizeof (struct ip_mreq)))
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#endif

#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}


/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    join6
 * Signature: (I[B)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_join6 (JNIEnv *env,
                                           jclass clazz __attribute__((unused)),
                                           jint fd, jbyteArray addr)
{
#ifdef HAVE_SETSOCKOPT
#ifdef HAVE_INET6
  struct ipv6_mreq maddr;
  jbyte *addr_elems;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  memcpy (&(maddr.ipv6mr_multiaddr.s6_addr), addr_elems, 16);
  maddr.ipv6mr_interface = 0;

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
                        &maddr, sizeof (struct ipv6_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) addr;
  THROW_NO_IPV6(env);
#endif /* HAVE_INET6 */
#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    leave
 * Signature: (I[B)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_leave (JNIEnv *env,
                                           jclass c __attribute__((unused)),
                                           jint fd, jbyteArray addr)
{
#ifdef HAVE_SETSOCKOPT
  struct ip_mreq maddr;
  jbyte *addr_elems;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  maddr.imr_multiaddr.s_addr = * ((uint32_t *) addr_elems);
  maddr.imr_interface.s_addr = INADDR_ANY;

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
                        &maddr, sizeof (struct ip_mreq)))
#ifdef _WIN32_WCE
	  JCL_ThrowExceptionBasedOnErrorCode(env, IO_EXCEPTION, WSAGetLastError());
#else
      JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#endif

#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    leave6
 * Signature: (I[B)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_leave6 (JNIEnv *env,
                                            jclass c __attribute__((unused)),
                                            jint fd, jbyteArray addr)
{
#ifdef HAVE_SETSOCKOPT
#ifdef HAVE_INET6
  struct ipv6_mreq maddr;
  jbyte *addr_elems;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  memcpy (&(maddr.ipv6mr_multiaddr.s6_addr), addr_elems, 16);
  maddr.ipv6mr_interface = 0;

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
                        &maddr, sizeof (struct ipv6_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) addr;
  THROW_NO_IPV6(env);
#endif /* HAVE_INET6 */
#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

static uint32_t getif_address (JNIEnv *env, const char *ifname);
static int getif_index (JNIEnv *env, const char *ifname);

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    joinGroup
 * Signature: (I[BILjava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_joinGroup (JNIEnv *env,
                                               jclass c __attribute__((unused)),
                                               jint fd, jbyteArray addr,
                                               jstring ifname)
{
#ifdef HAVE_SETSOCKOPT
  struct ip_mreq maddr;
  jbyte *addr_elems;
  const char *str_ifname;

  if (ifname != NULL)
    {
      str_ifname = JCL_jstring_to_cstring(env, ifname);
      maddr.imr_interface.s_addr = getif_address (env, str_ifname);
      JCL_free_cstring(env, ifname, str_ifname);

      if ((*env)->ExceptionCheck (env))
        return;
    }
  else
    maddr.imr_interface.s_addr = INADDR_ANY;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  maddr.imr_multiaddr.s_addr = * ((uint32_t *) addr_elems);

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                        &maddr, sizeof (struct ip_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));

#else
  (void) fd;
  (void) addr;
  (void) ifname;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    joinGroup6
 * Signature: (I[BILjava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_joinGroup6 (JNIEnv *env,
                                                jclass c __attribute__((unused)),
                                                jint fd, jbyteArray addr,
                                                jstring ifname)
{
#ifdef HAVE_SETSOCKOPT
#ifdef HAVE_INET6
  struct ipv6_mreq maddr;
  jbyte *addr_elems;
  const char *str_ifname;

  if (ifname == NULL)
    {
      str_ifname = JCL_jstring_to_cstring(env, ifname);
      maddr.ipv6mr_interface = getif_index (env, str_ifname);
      JCL_free_cstring(env, ifname, str_ifname);

      if ((*env)->ExceptionCheck (env))
        return;
    }
  else
    maddr.ipv6mr_interface = 0;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  memcpy (&(maddr.ipv6mr_multiaddr.s6_addr), addr_elems, 16);

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
                        &maddr, sizeof (struct ipv6_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) addr;
  THROW_NO_IPV6(env);
#endif /* HAVE_INET6 */
#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    leaveGroup
 * Signature: (I[BILjava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_leaveGroup (JNIEnv *env,
                                                jclass c __attribute__((unused)),
                                                jint fd, jbyteArray addr,
                                                jstring ifname)
{
#ifdef HAVE_SETSOCKOPT
  struct ip_mreq maddr;
  jbyte *addr_elems;
  const char *str_ifname;

  if (ifname != NULL)
    {
      str_ifname = JCL_jstring_to_cstring(env, ifname);
      maddr.imr_interface.s_addr = getif_address (env, str_ifname);
      JCL_free_cstring(env, ifname, str_ifname);

      if ((*env)->ExceptionCheck (env))
        return;
    }
  else
    maddr.imr_interface.s_addr = INADDR_ANY;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  maddr.imr_multiaddr.s_addr = * ((uint32_t *) addr_elems);

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
                        &maddr, sizeof (struct ip_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) addr;
  (void) ifname;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

/*
 * Class:     gnu_java_net_VMPlainSocketImpl
 * Method:    leaveGroup6
 * Signature: (I[BILjava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_gnu_java_net_VMPlainSocketImpl_leaveGroup6 (JNIEnv *env,
                                                jclass c __attribute__((unused)),
                                                jint fd, jbyteArray addr,
                                                jstring ifname)
{
#ifdef HAVE_SETSOCKOPT
#ifdef HAVE_INET6
  struct ipv6_mreq maddr;
  jbyte *addr_elems;
  const char *str_ifname;

  if (ifname == NULL)
    {
      str_ifname = JCL_jstring_to_cstring(env, ifname);
      maddr.ipv6mr_interface = getif_index (env, str_ifname);
      JCL_free_cstring(env, ifname, str_ifname);

      if ((*env)->ExceptionCheck (env))
        return;
    }
  else
    maddr.ipv6mr_interface = 0;

  addr_elems = (*env)->GetByteArrayElements (env, addr, NULL);
  if (addr_elems == NULL)
    return;

  memcpy (&(maddr.ipv6mr_multiaddr.s6_addr), addr_elems, 16);

  (*env)->ReleaseByteArrayElements (env, addr, addr_elems, JNI_ABORT);

  if (-1 == setsockopt (fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
                        &maddr, sizeof (struct ipv6_mreq)))
    JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
#else
  (void) fd;
  (void) addr;
  THROW_NO_IPV6(env);
#endif /* HAVE_INET6 */
#else
  (void) fd;
  (void) addr;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "socket options not supported");
#endif /* HAVE_SETSOCKOPT */
}

static uint32_t
getif_address (JNIEnv *env, const char *ifname)
{
#if defined (HAVE_IFADDRS_H) && defined (HAVE_GETIFADDRS)
  struct ifaddrs *ifaddrs, *i;
  uint32_t addr = 0;
  int foundaddr = 0;

  if (getifaddrs (&ifaddrs) == -1)
    {
      JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
      return 0;
    }

  for (i = ifaddrs; i != NULL; i = i->ifa_next)
    {
      if (strcmp (ifname, i->ifa_name) == 0)
        {
          /* Matched the name; see if there is an IPv4 address. */
          if (i->ifa_addr->sa_family == AF_INET)
            {
              foundaddr = 1;
              addr = ((struct sockaddr_in *) i->ifa_addr)->sin_addr.s_addr;
              break;
            }
        }
    }

  if (!foundaddr)
    JCL_ThrowException (env, SOCKET_EXCEPTION, "interface has no IPv4 address");

  freeifaddrs (ifaddrs);

  return addr;
#else
  (void) ifname;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "getifaddrs not available");
  return 0;
#endif /* HAVE_IFADDRS_H && HAVE_GETIFADDRS */
}

static int
getif_index (JNIEnv *env, const char *ifname)
{
#if defined (HAVE_IFADDRS_H) && defined (HAVE_GETIFADDRS)
  struct ifaddrs *ifaddrs, *i;
  char *lastname = NULL;
  int index = 1;
  int foundname = 0;

  if (getifaddrs (&ifaddrs) == -1)
    {
      JCL_ThrowException (env, SOCKET_EXCEPTION, strerror (errno));
      return -1;
    }

  lastname = ifaddrs->ifa_name;
  for (i = ifaddrs; i != NULL; i = i->ifa_next)
    {
      if (strcmp (lastname, ifaddrs->ifa_name) != 0)
        {
          lastname = ifaddrs->ifa_name;
          index++;
        }
      if (strcmp (ifname, ifaddrs->ifa_name) == 0)
        {
          foundname = 1;
          break;
        }
    }

  if (!foundname)
    JCL_ThrowException (env, SOCKET_EXCEPTION,
                        "no interface with that name");

  freeifaddrs (ifaddrs);

  return index;
#else
  (void) ifname;
  JCL_ThrowException (env, "java/lang/InternalError",
                      "getifaddrs not available");
  return -1;
#endif /* HAVE_GETIFADDRS */
}
