/*
 * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.net;

import java.io.FileDescriptor;
import static ikvm.internal.JNI.*;
import static ikvm.internal.Winsock.*;
import static java.net.net_util_md.*;
import static java.net.InetAddress.IPv4;
import static java.net.InetAddress.IPv6;

final class TwoStacksPlainDatagramSocketImpl_c
{
static final int ni_class = 0;
static final int JVM_IO_ERR = -1;
static final int JVM_IO_INTR = -2;

static final int java_net_SocketOptions_SO_BINDADDR = SocketOptions.SO_BINDADDR;
static final int java_net_SocketOptions_SO_SNDBUF = SocketOptions.SO_SNDBUF;
static final int java_net_SocketOptions_SO_RCVBUF = SocketOptions.SO_RCVBUF;
static final int java_net_SocketOptions_IP_TOS = SocketOptions.IP_TOS;
static final int java_net_SocketOptions_SO_REUSEADDR = SocketOptions.SO_REUSEADDR;
static final int java_net_SocketOptions_SO_BROADCAST = SocketOptions.SO_BROADCAST;
static final int java_net_SocketOptions_IP_MULTICAST_LOOP = SocketOptions.IP_MULTICAST_LOOP;
static final int java_net_SocketOptions_IP_MULTICAST_IF = SocketOptions.IP_MULTICAST_IF;
static final int java_net_SocketOptions_IP_MULTICAST_IF2 = SocketOptions.IP_MULTICAST_IF2;

/*

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/types.h>

#ifndef IPTOS_TOS_MASK
#define IPTOS_TOS_MASK 0x1e
#endif
#ifndef IPTOS_PREC_MASK
#define IPTOS_PREC_MASK 0xe0
#endif

#include "java_net_TwoStacksPlainDatagramSocketImpl.h"
#include "java_net_SocketOptions.h"
#include "java_net_NetworkInterface.h"

#include "jvm.h"
#include "jni_util.h"
#include "net_util.h"

#define IN_CLASSD(i)    (((long)(i) & 0xf0000000) == 0xe0000000)
#define IN_MULTICAST(i) IN_CLASSD(i)

*/

static boolean IN_MULTICAST(int ipv4address) {
    return ((ipv4address >> 24) & 0xf0) == 0xe0;
}

/************************************************************************
 * TwoStacksPlainDatagramSocketImpl
 */

/*
static jfieldID IO_fd_fdID;
static jfieldID pdsi_trafficClassID;
jfieldID pdsi_fdID;
jfieldID pdsi_fd1ID;
jfieldID pdsi_fduseID;
jfieldID pdsi_lastfdID;
jfieldID pdsi_timeoutID;

jfieldID pdsi_localPortID;
jfieldID pdsi_connected;

static jclass ia4_clazz;
static jmethodID ia4_ctor;

static CRITICAL_SECTION sizeCheckLock;
*/

/* Windows OS version is XP or better */
static final boolean xp_or_later = true;
/* Windows OS version is Windows 2000 or better */
//static int w2k_or_later = 0;

/*
 * Notes about UDP/IPV6 on Windows (XP and 2003 server):
 *
 * fd always points to the IPv4 fd, and fd1 points to the IPv6 fd.
 * Both fds are used when we bind to a wild-card address. When a specific
 * address is used, only one of them is used.
 */

/*
 * Returns a java.lang.Integer based on 'i'
 */
/*
jobject createInteger(JNIEnv *env, int i) {
    static jclass i_class;
    static jmethodID i_ctrID;
    static jfieldID i_valueID;

    if (i_class == NULL) {
        jclass c = (*env)->FindClass(env, "java/lang/Integer");
        CHECK_NULL_RETURN(c, NULL);
        i_ctrID = (*env)->GetMethodID(env, c, "<init>", "(I)V");
        CHECK_NULL_RETURN(i_ctrID, NULL);
        i_class = (*env)->NewGlobalRef(env, c);
        CHECK_NULL_RETURN(i_class, NULL);
    }

    return ( (*env)->NewObject(env, i_class, i_ctrID, i) );
}
*/

/*
 * Returns a java.lang.Boolean based on 'b'
 */
/*
jobject createBoolean(JNIEnv *env, int b) {
    static jclass b_class;
    static jmethodID b_ctrID;
    static jfieldID b_valueID;

    if (b_class == NULL) {
        jclass c = (*env)->FindClass(env, "java/lang/Boolean");
        CHECK_NULL_RETURN(c, NULL);
        b_ctrID = (*env)->GetMethodID(env, c, "<init>", "(Z)V");
        CHECK_NULL_RETURN(b_ctrID, NULL);
        b_class = (*env)->NewGlobalRef(env, c);
        CHECK_NULL_RETURN(b_class, NULL);
    }

    return( (*env)->NewObject(env, b_class, b_ctrID, (jboolean)(b!=0)) );
}
*/


static cli.System.Net.Sockets.Socket getFD(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this) {
    FileDescriptor fdObj = _this.fd;

    if (fdObj == NULL) {
        return null;
    }
    return fdObj.getSocket();
}

static cli.System.Net.Sockets.Socket getFD1(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this) {
    FileDescriptor fdObj = _this.fd1;

    if (fdObj == NULL) {
        return null;
    }
    return fdObj.getSocket();
}

/*
 * This function returns JNI_TRUE if the datagram size exceeds the underlying
 * provider's ability to send to the target address. The following OS
 * oddies have been observed :-
 *
 * 1. On Windows 95/98 if we try to send a datagram > 12k to an application
 *    on the same machine then the send will fail silently.
 *
 * 2. On Windows ME if we try to send a datagram > supported by underlying
 *    provider then send will not return an error.
 *
 * 3. On Windows NT/2000 if we exceeds the maximum size then send will fail
 *    with WSAEADDRNOTAVAIL.
 *
 * 4. On Windows 95/98 if we exceed the maximum size when sending to
 *    another machine then WSAEINVAL is returned.
 *
 */
/*
jboolean exceedSizeLimit(JNIEnv *env, jint fd, jint addr, jint size)
{
#define DEFAULT_MSG_SIZE        65527
    static jboolean initDone;
    static jboolean is95or98;
    static int maxmsg;

    typedef struct _netaddr  {          /* Windows 95/98 only *-/
        unsigned long addr;
        struct _netaddr *next;
    } netaddr;
    static netaddr *addrList;
    netaddr *curr;

    /*
     * First time we are called we must determine which OS this is and also
     * get the maximum size supported by the underlying provider.
     *
     * In addition on 95/98 we must enumerate our IP addresses.
     *-/
    if (!initDone) {
        EnterCriticalSection(&sizeCheckLock);

        if (initDone) {
            /* another thread got there first *-/
            LeaveCriticalSection(&sizeCheckLock);

        } else {
            OSVERSIONINFO ver;
            int len;

            /*
             * Step 1: Determine which OS this is.
             *-/
            ver.dwOSVersionInfoSize = sizeof(ver);
            GetVersionEx(&ver);

            is95or98 = JNI_FALSE;
            if (ver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
                ver.dwMajorVersion == 4 &&
                (ver.dwMinorVersion == 0 || ver.dwMinorVersion == 10)) {

                is95or98 = JNI_TRUE;
            }

            /*
             * Step 2: Determine the maximum datagram supported by the
             * underlying provider. On Windows 95 if winsock hasn't been
             * upgraded (ie: unsupported configuration) then we assume
             * the default 64k limit.
             *-/
            len = sizeof(maxmsg);
            if (NET_GetSockOpt(fd, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *)&maxmsg, &len) < 0) {
                maxmsg = DEFAULT_MSG_SIZE;
            }

            /*
             * Step 3: On Windows 95/98 then enumerate the IP addresses on
             * this machine. This is necesary because we need to check if the
             * datagram is being sent to an application on the same machine.
             *-/
            if (is95or98) {
                char hostname[255];
                struct hostent *hp;

                if (gethostname(hostname, sizeof(hostname)) == -1) {
                    LeaveCriticalSection(&sizeCheckLock);
                    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Unable to obtain hostname");
                    return JNI_TRUE;
                }
                hp = (struct hostent *)gethostbyname(hostname);
                if (hp != NULL) {
                    struct in_addr **addrp = (struct in_addr **) hp->h_addr_list;

                    while (*addrp != (struct in_addr *) 0) {
                        curr = (netaddr *)malloc(sizeof(netaddr));
                        if (curr == NULL) {
                            while (addrList != NULL) {
                                curr = addrList->next;
                                free(addrList);
                                addrList = curr;
                            }
                            LeaveCriticalSection(&sizeCheckLock);
                            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
                            return JNI_TRUE;
                        }
                        curr->addr = htonl((*addrp)->S_un.S_addr);
                        curr->next = addrList;
                        addrList = curr;
                        addrp++;
                    }
                }
            }

            /*
             * Step 4: initialization is done so set flag and unlock cs
             *-/
            initDone = JNI_TRUE;
            LeaveCriticalSection(&sizeCheckLock);
        }
    }

    /*
     * Now examine the size of the datagram :-
     *
     * (a) If exceeds size of service provider return 'false' to indicate that
     *     we exceed the limit.
     * (b) If not 95/98 then return 'true' to indicate that the size is okay.
     * (c) On 95/98 if the size is <12k we are okay.
     * (d) On 95/98 if size > 12k then check if the destination is the current
     *     machine.
     *-/
    if (size > maxmsg) {        /* step (a) *-/
        return JNI_TRUE;
    }
    if (!is95or98) {            /* step (b) *-/
        return JNI_FALSE;
    }
    if (size <= 12280) {        /* step (c) *-/
        return JNI_FALSE;
    }

    /* step (d) *-/

    if ((addr & 0x7f000000) == 0x7f000000) {
        return JNI_TRUE;
    }
    curr = addrList;
    while (curr != NULL) {
        if (curr->addr == addr) {
            return JNI_TRUE;
        }
        curr = curr->next;
    }
    return JNI_FALSE;
}
*/

/*
 * Return JNI_TRUE if this Windows edition supports ICMP Port Unreachable
 */
static boolean supportPortUnreachable() {
    // we don't support anything pre-Win2K anyway
    return true;
}

/*
 * This function "purges" all outstanding ICMP port unreachable packets
 * outstanding on a socket and returns JNI_TRUE if any ICMP messages
 * have been purged. The rational for purging is to emulate normal BSD
 * behaviour whereby receiving a "connection reset" status resets the
 * socket.
 */
static boolean purgeOutstandingICMP(cli.System.Net.Sockets.Socket fd)
{
    boolean got_icmp = false;
    byte[] buf = new byte[1];
    fd_set tbl = new fd_set();
    timeval t = new timeval();
    SOCKETADDRESS rmtaddr = null;

    /*
     * A no-op if this OS doesn't support it.
     */
    if (!supportPortUnreachable()) {
        return JNI_FALSE;
    }

    /*
     * Peek at the queue to see if there is an ICMP port unreachable. If there
     * is then receive it.
     */
    FD_ZERO(tbl);
    FD_SET(fd, tbl);
    while(true) {
        if (select(tbl, null, null, t) <= 0) {
            break;
        }
        if (recvfrom(fd, buf, 1, MSG_PEEK,
                         rmtaddr) != JVM_IO_ERR) {
            break;
        }
        if (WSAGetLastError() != WSAECONNRESET) {
            /* some other error - we don't care here */
            break;
        }

        recvfrom(fd, buf, 1, 0, rmtaddr);
        got_icmp = JNI_TRUE;
    }

    return got_icmp;
}


/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    init
 * Signature: ()V
 */
/*
JNIEXPORT void JNICALL
Java_java_net_TwoStacksPlainDatagramSocketImpl_init(JNIEnv *env, jclass cls) {

    OSVERSIONINFO ver;
    int version;
    ver.dwOSVersionInfoSize = sizeof(ver);
    GetVersionEx(&ver);

    version = ver.dwMajorVersion * 10 + ver.dwMinorVersion;
    xp_or_later = (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (version >= 51);
    w2k_or_later = (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (version >= 50);

    /* get fieldIDs *-/
    pdsi_fdID = (*env)->GetFieldID(env, cls, "fd", "Ljava/io/FileDescriptor;");
    CHECK_NULL(pdsi_fdID);
    pdsi_fd1ID = (*env)->GetFieldID(env, cls, "fd1", "Ljava/io/FileDescriptor;");
    CHECK_NULL(pdsi_fd1ID);
    pdsi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I");
    CHECK_NULL(pdsi_timeoutID);
    pdsi_fduseID = (*env)->GetFieldID(env, cls, "fduse", "I");
    CHECK_NULL(pdsi_fduseID);
    pdsi_lastfdID = (*env)->GetFieldID(env, cls, "lastfd", "I");
    CHECK_NULL(pdsi_lastfdID);
    pdsi_trafficClassID = (*env)->GetFieldID(env, cls, "trafficClass", "I");
    CHECK_NULL(pdsi_trafficClassID);
    pdsi_localPortID = (*env)->GetFieldID(env, cls, "localPort", "I");
    CHECK_NULL(pdsi_localPortID);
    pdsi_connected = (*env)->GetFieldID(env, cls, "connected", "Z");
    CHECK_NULL(pdsi_connected);

    cls = (*env)->FindClass(env, "java/io/FileDescriptor");
    CHECK_NULL(cls);
    IO_fd_fdID = NET_GetFileDescriptorID(env);
    CHECK_NULL(IO_fd_fdID);

    ia4_clazz = (*env)->FindClass(env, "java/net/Inet4Address");
    CHECK_NULL(ia4_clazz);
    ia4_clazz = (*env)->NewGlobalRef(env, ia4_clazz);
    CHECK_NULL(ia4_clazz);
    ia4_ctor = (*env)->GetMethodID(env, ia4_clazz, "<init>", "()V");
    CHECK_NULL(ia4_ctor);


    InitializeCriticalSection(&sizeCheckLock);
}
*/

static void bind0(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, int port, InetAddress addressObj) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;

    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    int family;
    boolean ipv6_supported = ipv6_available();

    SOCKETADDRESS lcladdr;
    lcladdr = new SOCKETADDRESS();

    if (IS_NULL(addressObj)) {
        JNU_ThrowNullPointerException(env, "argument address");
        return;
    }

    family = addressObj.family;
    if (family == IPv6 && !ipv6_supported) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Protocol family not supported");
        return;
    }

    if (IS_NULL(fdObj) || (ipv6_supported && IS_NULL(fd1Obj))) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "socket closed");
        return;
    } else {
        fd = fdObj.getSocket();
        if (ipv6_supported) {
            fd1 = fd1Obj.getSocket();
        }
    }
    if (IS_NULL(addressObj)) {
        JNU_ThrowNullPointerException(env, "argument address");
        return;
    }

    if (NET_InetAddressToSockaddr(env, addressObj, port, lcladdr, JNI_FALSE) != 0) {
      return;
    }

    if (ipv6_supported) {
        ipv6bind v6bind = new ipv6bind();
        v6bind.addr = lcladdr;
        v6bind.ipv4_fd = fd;
        v6bind.ipv6_fd = fd1;
        if (NET_BindV6(v6bind) != -1) {
            /* check if the fds have changed */
            if (v6bind.ipv4_fd != fd) {
                fd = v6bind.ipv4_fd;
                if (fd == null) {
                    /* socket is closed. */
                    _this.fd = null;
                } else {
                    /* socket was re-created */
                    fdObj.setSocket(fd);
                }
            }
            if (v6bind.ipv6_fd != fd1) {
                fd1 = v6bind.ipv6_fd;
                if (fd1 == null) {
                    /* socket is closed. */
                    _this.fd1 = null;
                } else {
                    /* socket was re-created */
                    fd1Obj.setSocket(fd1);
                }
            }
        } else {
            NET_ThrowCurrent (env, "Cannot bind");
            return;
        }
    } else {
        if (bind(fd, lcladdr) == -1) {
            if (WSAGetLastError() == WSAEACCES) {
                WSASetLastError(WSAEADDRINUSE);
            }
            NET_ThrowCurrent(env, "Cannot bind");
            return;
        }
    }

    if (port == 0) {
        if (fd == null) {
            /* must be an IPV6 only socket. */
            fd = fd1;
        }
        if (getsockname(fd, lcladdr) == -1) {
            NET_ThrowCurrent(env, "JVM_GetSockName");
            return;
        }
        port = ntohs(GET_PORT (lcladdr));
    }
    _this.localPort = port;
}


/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    connect0
 * Signature: (Ljava/net/InetAddress;I)V
 */

static void connect0(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, InetAddress address, int port) {
    /* The object's field */
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    /* The fdObj'fd */
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    cli.System.Net.Sockets.Socket fdc;
    /* The packetAddress address, family and port */
    int addr, family;
    SOCKETADDRESS rmtaddr;
    rmtaddr = new SOCKETADDRESS();
    boolean ipv6_supported = ipv6_available();

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    }
    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }
    if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
    }

    if (IS_NULL(address)) {
        JNU_ThrowNullPointerException(env, "address");
        return;
    }

    addr = address.address;

    family = address.family;
    if (family == IPv6 && !ipv6_supported) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Protocol family not supported");
        return;
    }

    fdc = family == IPv4? fd: fd1;

    if (xp_or_later) {
        /* SIO_UDP_CONNRESET fixes a bug introduced in Windows 2000, which
         * returns connection reset errors un connected UDP sockets (as well
         * as connected sockets. The solution is to only enable this feature
         * when the socket is connected
         */
        WSAIoctl(fdc, SIO_UDP_CONNRESET, true);
    }

    if (NET_InetAddressToSockaddr(env, address, port, rmtaddr, JNI_FALSE) != 0) {
      return;
    }

    if (connect(fdc, rmtaddr) == -1) {
        NET_ThrowCurrent(env, "connect");
        return;
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    disconnect0
 * Signature: ()V
 */

static void disconnect0(TwoStacksPlainDatagramSocketImpl _this, int family) {
    /* The object's field */
    FileDescriptor fdObj;
    /* The fdObj'fd */
    cli.System.Net.Sockets.Socket fd;
    SOCKETADDRESS addr;
    addr = new SOCKETADDRESS();

    if (family == IPv4) {
        fdObj = _this.fd;
    } else {
        fdObj = _this.fd1;
    }

    if (IS_NULL(fdObj)) {
        /* disconnect doesn't throw any exceptions */
        return;
    }
    fd = fdObj.getSocket();

    connect(fd, addr);

    /*
     * use SIO_UDP_CONNRESET
     * to disable ICMP port unreachable handling here.
     */
    if (xp_or_later) {
        WSAIoctl(fd,SIO_UDP_CONNRESET,false);
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    send
 * Signature: (Ljava/net/DatagramPacket;)V
 */
static void send(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, DatagramPacket packet) {
    FileDescriptor fdObj;
    cli.System.Net.Sockets.Socket fd;

    InetAddress iaObj;
    int address;
    int family;

    int packetBufferOffset, packetBufferLen, packetPort;
    byte[] packetBuffer;
    boolean connected;

    SOCKETADDRESS rmtaddr;
    rmtaddr = new SOCKETADDRESS();

    if (IS_NULL(packet)) {
        JNU_ThrowNullPointerException(env, "null packet");
        return;
    }

    iaObj = packet.address;

    packetPort = packet.port;
    packetBufferOffset = packet.offset;
    packetBuffer = packet.buf;
    connected = _this.connected;

    if (IS_NULL(iaObj) || IS_NULL(packetBuffer)) {
        JNU_ThrowNullPointerException(env, "null address || null buffer");
        return;
    }

    family = iaObj.family;
    if (family == IPv4) {
        fdObj = _this.fd;
    } else {
        if (!ipv6_available()) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Protocol not allowed");
            return;
        }
        fdObj = _this.fd1;
    }

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    }
    fd = fdObj.getSocket();

    packetBufferLen = packet.length;

    if (connected) {
        rmtaddr = null;
    } else {
      if (NET_InetAddressToSockaddr(env, iaObj, packetPort, rmtaddr, JNI_FALSE) != 0) {
        return;
      }
    }

    /*
    if (packetBufferLen > MAX_BUFFER_LEN) {

        /*
         * On 95/98 if we try to send a datagram >12k to an application
         * on the same machine then this will fail silently. Thus we
         * catch this situation here so that we can throw an exception
         * when this arises.
         * On ME if we try to send a datagram with a size greater than
         * that supported by the service provider then no error is
         * returned.
         *-/
        if (!w2k_or_later) { /* avoid this check on Win 2K or better. Does not work with IPv6.
                      * Check is not necessary on these OSes *-/
            if (connected) {
                address = (*env)->GetIntField(env, iaObj, ia_addressID);
            } else {
                address = ntohl(rmtaddr.him4.sin_addr.s_addr);
            }

            if (exceedSizeLimit(env, fd, address, packetBufferLen)) {
                if (!((*env)->ExceptionOccurred(env))) {
                    NET_ThrowNew(env, WSAEMSGSIZE, "Datagram send failed");
                }
                return;
            }
        }

        /* When JNI-ifying the JDK's IO routines, we turned
         * read's and write's of byte arrays of size greater
         * than 2048 bytes into several operations of size 2048.
         * This saves a malloc()/memcpy()/free() for big
         * buffers.  This is OK for file IO and TCP, but that
         * strategy violates the semantics of a datagram protocol.
         * (one big send) != (several smaller sends).  So here
         * we *must* alloc the buffer.  Note it needn't be bigger
         * than 65,536 (0xFFFF) the max size of an IP packet.
         * anything bigger is truncated anyway.
         *-/
        fullPacket = (char *)malloc(packetBufferLen);
        if (!fullPacket) {
            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
            return;
        }
    } else {
        fullPacket = &(BUF[0]);
    }
    */

    switch (sendto(fd, packetBuffer, packetBufferOffset, packetBufferLen, 0, rmtaddr)) {
        case JVM_IO_ERR:
            NET_ThrowCurrent(env, "Datagram send failed");
            break;

        case JVM_IO_INTR:
            JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                            "operation interrupted");
    }
}

/*
 * check which socket was last serviced when there was data on both sockets.
 * Only call this if sure that there is data on both sockets.
 */
private static cli.System.Net.Sockets.Socket checkLastFD (TwoStacksPlainDatagramSocketImpl _this, cli.System.Net.Sockets.Socket fd, cli.System.Net.Sockets.Socket fd1) {
    cli.System.Net.Sockets.Socket nextfd, lastfd = _this.lastfd;
    if (lastfd == null) {
        /* arbitrary. Choose fd */
        _this.lastfd = fd;
        return fd;
    } else {
        if (lastfd == fd) {
            nextfd = fd1;
        } else {
            nextfd = fd;
        }
        _this.lastfd = nextfd;
        return nextfd;
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    peek
 * Signature: (Ljava/net/InetAddress;)I
 */
static int peek(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, InetAddress addressObj) {
    FileDescriptor fdObj = _this.fd;
    int timeout = _this.timeout;
    cli.System.Net.Sockets.Socket fd;

    /* The address and family fields of addressObj */
    int address, family;

    int n;
    SOCKETADDRESS remote_addr = new SOCKETADDRESS();
    byte[] buf = new byte[1];
    boolean retry;
    long prevTime = 0;

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Socket closed");
        return -1;
    } else {
        fd = fdObj.getSocket();
        if (fd == null) {
           JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                           "socket closed");
           return -1;
        }
    }
    if (IS_NULL(addressObj)) {
        JNU_ThrowNullPointerException(env, "Null address in peek()");
        return -1;
    } else {
        address = addressObj.address;
        /* We only handle IPv4 for now. Will support IPv6 once its in the os */
        family = AF_INET;
    }

    do {
        retry = FALSE;

        /*
         * If a timeout has been specified then we select on the socket
         * waiting for a read event or a timeout.
         */
        if (timeout != 0) {
            int ret;
            prevTime = JVM_CurrentTimeMillis(env, 0);
            ret = NET_Timeout (fd, timeout);
            if (ret == 0) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                "Peek timed out");
                return ret;
            } else if (ret == JVM_IO_ERR) {
                NET_ThrowCurrent(env, "timeout in datagram socket peek");
                return ret;
            } else if (ret == JVM_IO_INTR) {
                JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                "operation interrupted");
                return ret;
            }
        }

        /* now try the peek */
        n = recvfrom(fd, buf, 1, MSG_PEEK,
                         remote_addr);

        if (n == JVM_IO_ERR) {
            if (WSAGetLastError() == WSAECONNRESET) {
                boolean connected;

                /*
                 * An icmp port unreachable - we must receive this as Windows
                 * does not reset the state of the socket until this has been
                 * received.
                 */
                purgeOutstandingICMP(fd);

                connected =  _this.connected;
                if (connected) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"PortUnreachableException",
                                       "ICMP Port Unreachable");
                    return 0;
                }

                /*
                 * If a timeout was specified then we need to adjust it because
                 * we may have used up some of the timeout befor the icmp port
                 * unreachable arrived.
                 */
                if (timeout != 0) {
                    long newTime = JVM_CurrentTimeMillis(env, 0);
                    timeout -= (newTime - prevTime);
                    if (timeout <= 0) {
                        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                "Receive timed out");
                        return 0;
                    }
                    prevTime = newTime;
                }

                /* Need to retry the recv */
                retry = TRUE;
            }
        }
    } while (retry);

    if (n == JVM_IO_ERR && WSAGetLastError() != WSAEMSGSIZE) {
        NET_ThrowCurrent(env, "Datagram peek failed");
        return 0;
    }
    if (n == JVM_IO_INTR) {
        JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException", null);
        return 0;
    }
    addressObj.address = ntohl(remote_addr.sin_addr.s_addr);
    addressObj.family = IPv4;

    /* return port */
    return ntohs(remote_addr.sin_port);
}

static int peekData(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, DatagramPacket packet) {

    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    int timeout = _this.timeout;

    byte[] packetBuffer;
    int packetBufferOffset, packetBufferLen;

    cli.System.Net.Sockets.Socket fd = null, fd1 = null, fduse = null;
    int nsockets=0, errorCode;
    int port;
    byte[] data;

    boolean checkBoth = false;
    int datalen;
    int n;
    SOCKETADDRESS remote_addr;
    remote_addr = new SOCKETADDRESS();
    boolean retry;
    long prevTime = 0;

    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
        if (fd == null) {
           JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                           "socket closed");
           return -1;
        }
        nsockets = 1;
    }

    if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
        if (fd1 == null) {
           JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                           "socket closed");
           return -1;
        }
        nsockets ++;
    }

    switch (nsockets) {
      case 0:
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                       "socket closed");
        return -1;
      case 1:
        if (!IS_NULL(fdObj)) {
           fduse = fd;
        } else {
           fduse = fd1;
        }
        break;
      case 2:
        checkBoth = TRUE;
        break;
    }

    if (IS_NULL(packet)) {
        JNU_ThrowNullPointerException(env, "packet");
        return -1;
    }

    packetBuffer = packet.buf;

    if (IS_NULL(packetBuffer)) {
        JNU_ThrowNullPointerException(env, "packet buffer");
        return -1;
    }

    packetBufferOffset = packet.offset;
    packetBufferLen = packet.bufLength;

    /*
    if (packetBufferLen > MAX_BUFFER_LEN) {

        /* When JNI-ifying the JDK's IO routines, we turned
         * read's and write's of byte arrays of size greater
         * than 2048 bytes into several operations of size 2048.
         * This saves a malloc()/memcpy()/free() for big
         * buffers.  This is OK for file IO and TCP, but that
         * strategy violates the semantics of a datagram protocol.
         * (one big send) != (several smaller sends).  So here
         * we *must* alloc the buffer.  Note it needn't be bigger
         * than 65,536 (0xFFFF) the max size of an IP packet.
         * anything bigger is truncated anyway.
         *-/
        fullPacket = (char *)malloc(packetBufferLen);
        if (!fullPacket) {
            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
            return -1;
        }
    } else {
        fullPacket = &(BUF[0]);
    }
    */

    do {
        int ret;
        retry = FALSE;

        /*
         * If a timeout has been specified then we select on the socket
         * waiting for a read event or a timeout.
         */
        if (checkBoth) {
            int t = timeout == 0 ? -1: timeout;
            prevTime = JVM_CurrentTimeMillis(env, 0);
            cli.System.Net.Sockets.Socket[] tmp = new cli.System.Net.Sockets.Socket[] { fduse };
            ret = NET_Timeout2 (fd, fd1, t, tmp);
            fduse = tmp[0];
            /* all subsequent calls to recv() or select() will use the same fd
             * for this call to peek() */
            if (ret <= 0) {
                if (ret == 0) {
                    JNU_ThrowByName(env,JNU_JAVANETPKG+"SocketTimeoutException",
                                        "Peek timed out");
                } else if (ret == JVM_IO_ERR) {
                    NET_ThrowCurrent(env, "timeout in datagram socket peek");
                } else if (ret == JVM_IO_INTR) {
                    JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                    "operation interrupted");
                }
                return -1;
            }
            if (ret == 2) {
                fduse = checkLastFD (_this, fd, fd1);
            }
            checkBoth = FALSE;
        } else if (timeout != 0) {
            if (prevTime == 0) {
                prevTime = JVM_CurrentTimeMillis(env, 0);
            }
            ret = NET_Timeout (fduse, timeout);
            if (ret <= 0) {
                if (ret == 0) {
                    JNU_ThrowByName(env,JNU_JAVANETPKG+"SocketTimeoutException",
                                    "Receive timed out");
                } else if (ret == JVM_IO_ERR) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                    "Socket closed");
                } else if (ret == JVM_IO_INTR) {
                    JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                    "operation interrupted");
                }
                return -1;
            }
        }

        /* receive the packet */
        n = recvfrom(fduse, packetBuffer, packetBufferOffset, packetBufferLen, MSG_PEEK, remote_addr);
        port = ntohs (GET_PORT(remote_addr));
        if (n == JVM_IO_ERR) {
            if (WSAGetLastError() == WSAECONNRESET) {
                boolean connected;

                /*
                 * An icmp port unreachable - we must receive this as Windows
                 * does not reset the state of the socket until this has been
                 * received.
                 */
                purgeOutstandingICMP(fduse);

                connected = _this.connected;
                if (connected) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"PortUnreachableException",
                                       "ICMP Port Unreachable");

                    return -1;
                }

                /*
                 * If a timeout was specified then we need to adjust it because
                 * we may have used up some of the timeout befor the icmp port
                 * unreachable arrived.
                 */
                if (timeout != 0) {
                    long newTime = JVM_CurrentTimeMillis(env, 0);
                    timeout -= (newTime - prevTime);
                    if (timeout <= 0) {
                        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                "Receive timed out");
                        return -1;
                    }
                    prevTime = newTime;
                }
                retry = TRUE;
            }
        }
    } while (retry);

    if (n < 0) {
        errorCode = WSAGetLastError();
        /* check to see if it's because the buffer was too small */
        if (errorCode == WSAEMSGSIZE) {
            /* it is because the buffer is too small. It's UDP, it's
             * unreliable, it's all good. discard the rest of the
             * data..
             */
            n = packetBufferLen;
        } else {
            /* failure */
            packet.length = 0;
        }
    }
    if (n == -1) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "socket closed");
    } else if (n == -2) {
        JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                        "operation interrupted");
    } else if (n < 0) {
        NET_ThrowCurrent(env, "Datagram receive failed");
    } else {
        InetAddress packetAddress;

        /*
         * Check if there is an InetAddress already associated with this
         * packet. If so we check if it is the same source address. We
         * can't update any existing InetAddress because it is immutable
         */
        packetAddress = packet.address;
        if (packetAddress != NULL) {
            if (!NET_SockaddrEqualsInetAddress(remote_addr, packetAddress)) {
                /* force a new InetAddress to be created */
                packetAddress = null;
            }
        }
        if (packetAddress == NULL) {
            int[] tmp = { port };
            packetAddress = NET_SockaddrToInetAddress(remote_addr, tmp);
            port = tmp[0];
            /* stuff the new Inetaddress in the packet */
            packet.address = packetAddress;
        }

        /* populate the packet */
        packet.port = port;
        packet.length = n;
    }

    /* make sure receive() picks up the right fd */
    _this.fduse = fduse;

    return port;
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    receive
 * Signature: (Ljava/net/DatagramPacket;)V
 */
static void receive0(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, DatagramPacket packet) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    int timeout = _this.timeout;
    byte[] packetBuffer;
    int packetBufferOffset, packetBufferLen;
    boolean ipv6_supported = ipv6_available();

    /* as a result of the changes for ipv6, peek() or peekData()
     * must be called prior to receive() so that fduse can be set.
     */
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    cli.System.Net.Sockets.Socket fduse = null;
    int errorCode;

    int n, nsockets=0;
    SOCKETADDRESS remote_addr;
    remote_addr = new SOCKETADDRESS();
    boolean retry;
    long prevTime = 0, selectTime=0;
    boolean connected;

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    }

    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
        nsockets ++;
    }
    if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
        nsockets ++;
    }

    if (nsockets == 2) { /* need to choose one of them */
        /* was fduse set in peek? */
        fduse = _this.fduse;
        if (fduse == null) {
            /* not set in peek(), must select on both sockets */
            int ret, t = (timeout == 0) ? -1: timeout;
            cli.System.Net.Sockets.Socket[] tmp = new cli.System.Net.Sockets.Socket[] { fduse };
            ret = NET_Timeout2 (fd, fd1, t, tmp);
            fduse = tmp[0];
            if (ret == 2) {
                fduse = checkLastFD (_this, fd, fd1);
            } else if (ret <= 0) {
                if (ret == 0) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                    "Receive timed out");
                } else if (ret == JVM_IO_ERR) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                    "Socket closed");
                } else if (ret == JVM_IO_INTR) {
                    JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                    "operation interrupted");
                }
                return;
            }
        }
    } else if (!ipv6_supported) {
        fduse = fd;
    } else if (IS_NULL(fdObj)) {
        /* ipv6 supported: and this socket bound to an IPV6 only address */
        fduse = fd1;
    } else {
        /* ipv6 supported: and this socket bound to an IPV4 only address */
        fduse = fd;
    }

    if (IS_NULL(packet)) {
        JNU_ThrowNullPointerException(env, "packet");
        return;
    }

    packetBuffer = packet.buf;

    if (IS_NULL(packetBuffer)) {
        JNU_ThrowNullPointerException(env, "packet buffer");
        return;
    }

    packetBufferOffset = packet.offset;
    packetBufferLen = packet.bufLength;

    /*
    if (packetBufferLen > MAX_BUFFER_LEN) {

        /* When JNI-ifying the JDK's IO routines, we turned
         * read's and write's of byte arrays of size greater
         * than 2048 bytes into several operations of size 2048.
         * This saves a malloc()/memcpy()/free() for big
         * buffers.  This is OK for file IO and TCP, but that
         * strategy violates the semantics of a datagram protocol.
         * (one big send) != (several smaller sends).  So here
         * we *must* alloc the buffer.  Note it needn't be bigger
         * than 65,536 (0xFFFF) the max size of an IP packet.
         * anything bigger is truncated anyway.
         *-/
        fullPacket = (char *)malloc(packetBufferLen);
        if (!fullPacket) {
            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
            return;
        }
    } else {
        fullPacket = &(BUF[0]);
    }
    */



    /*
     * If this Windows edition supports ICMP port unreachable and if we
     * are not connected then we need to know if a timeout has been specified
     * and if so we need to pick up the current time. These are required in
     * order to implement the semantics of timeout, viz :-
     * timeout set to t1 but ICMP port unreachable arrives in t2 where
     * t2 < t1. In this case we must discard the ICMP packets and then
     * wait for the next packet up to a maximum of t1 minus t2.
     */
    connected = _this.connected;
    if (supportPortUnreachable() && !connected && timeout != 0 &&!ipv6_supported) {
        prevTime = JVM_CurrentTimeMillis(env, 0);
    }

    if (timeout != 0 && nsockets == 1) {
        int ret;
        ret = NET_Timeout(fduse, timeout);
        if (ret <= 0) {
            if (ret == 0) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                "Receive timed out");
            } else if (ret == JVM_IO_ERR) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                "Socket closed");
            } else if (ret == JVM_IO_INTR) {
                JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                "operation interrupted");
            }
            return;
        }
    }

    /*
     * Loop only if we discarding ICMP port unreachable packets
     */
    do {
        retry = FALSE;

        /* receive the packet */
        n = recvfrom(fduse, packetBuffer, packetBufferOffset, packetBufferLen, 0, remote_addr);

        if (n == JVM_IO_ERR) {
            if (WSAGetLastError() == WSAECONNRESET) {
                /*
                 * An icmp port unreachable has been received - consume any other
                 * outstanding packets.
                 */
                purgeOutstandingICMP(fduse);

                /*
                 * If connected throw a PortUnreachableException
                 */

                if (connected) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"PortUnreachableException",
                                       "ICMP Port Unreachable");
                    return;
                }

                /*
                 * If a timeout was specified then we need to adjust it because
                 * we may have used up some of the timeout before the icmp port
                 * unreachable arrived.
                 */
                if (timeout != 0) {
                    int ret;
                    long newTime = JVM_CurrentTimeMillis(env, 0);
                    timeout -= (newTime - prevTime);
                    prevTime = newTime;

                    if (timeout <= 0) {
                        ret = 0;
                    } else {
                        ret = NET_Timeout(fduse, timeout);
                    }

                    if (ret <= 0) {
                        if (ret == 0) {
                            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketTimeoutException",
                                            "Receive timed out");
                        } else if (ret == JVM_IO_ERR) {
                            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                                            "Socket closed");
                        } else if (ret == JVM_IO_INTR) {
                            JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                                            "operation interrupted");
                        }
                        return;
                    }
                }

                /*
                 * An ICMP port unreachable was received but we are
                 * not connected so ignore it.
                 */
                retry = TRUE;
            }
        }
    } while (retry);

    if (n < 0) {
        errorCode = WSAGetLastError();
        /* check to see if it's because the buffer was too small */
        if (errorCode == WSAEMSGSIZE) {
            /* it is because the buffer is too small. It's UDP, it's
             * unreliable, it's all good. discard the rest of the
             * data..
             */
            n = packetBufferLen;
        } else {
            /* failure */
            packet.length = 0;
        }
    }
    if (n == -1) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "socket closed");
    } else if (n == -2) {
        JNU_ThrowByName(env, JNU_JAVAIOPKG+"InterruptedIOException",
                        "operation interrupted");
    } else if (n < 0) {
        NET_ThrowCurrent(env, "Datagram receive failed");
    } else {
        int port;
        InetAddress packetAddress;

        /*
         * Check if there is an InetAddress already associated with this
         * packet. If so we check if it is the same source address. We
         * can't update any existing InetAddress because it is immutable
         */
        packetAddress = packet.address;

        if (packetAddress != NULL) {
            if (!NET_SockaddrEqualsInetAddress(remote_addr, packetAddress)) {
                /* force a new InetAddress to be created */
                packetAddress = null;
            }
        }
        if (packetAddress == NULL) {
            int[] tmp = { 0 };
            packetAddress = NET_SockaddrToInetAddress(remote_addr, tmp);
            port = tmp[0];
            /* stuff the new Inetaddress in the packet */
            packet.address = packetAddress;
        } else {
            /* only get the new port number */
            port = NET_GetPortFromSockaddr(remote_addr);
        }
        /* populate the packet */
        packet.port = port;
        packet.length = n;
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    datagramSocketCreate
 * Signature: ()V
 */
static void datagramSocketCreate(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    boolean ipv6_supported = ipv6_available();

    if (IS_NULL(fdObj) || (ipv6_supported && IS_NULL(fd1Obj))) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Socket closed");
        return;
    } else {
        fd =  socket (AF_INET, SOCK_DGRAM, 0);
    }
    if (fd == INVALID_SOCKET) {
        NET_ThrowCurrent(env, "Socket creation failed");
        return;
    }
    fdObj.setSocket(fd);
    NET_SetSockOpt(fd, SOL_SOCKET, SO_BROADCAST, true);

    if (ipv6_supported) {
        /* SIO_UDP_CONNRESET fixes a bug introduced in Windows 2000, which
         * returns connection reset errors un connected UDP sockets (as well
         * as connected sockets. The solution is to only enable this feature
         * when the socket is connected
         */
        WSAIoctl(fd,SIO_UDP_CONNRESET,false);
        fd1 = socket (AF_INET6, SOCK_DGRAM, 0);
        if (fd1 == INVALID_SOCKET) {
            NET_ThrowCurrent(env, "Socket creation failed");
            return;
        }
        NET_SetSockOpt(fd1, SOL_SOCKET, SO_BROADCAST, true);
        WSAIoctl(fd1,SIO_UDP_CONNRESET,false);
        fd1Obj.setSocket(fd1);
    } else {
        /* drop the second fd */
        _this.fd1 = null;
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    datagramSocketClose
 * Signature: ()V
 */
static void datagramSocketClose(TwoStacksPlainDatagramSocketImpl _this) {
    /*
     * REMIND: PUT A LOCK AROUND THIS CODE
     */
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    boolean ipv6_supported = ipv6_available();
    cli.System.Net.Sockets.Socket fd = null, fd1 = null;

    if (IS_NULL(fdObj) && (!ipv6_supported || IS_NULL(fd1Obj))) {
        return;
    }

    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
        if (fd != null) {
            fdObj.setSocket(null);
            NET_SocketClose(fd);
        }
    }

    if (ipv6_supported && fd1Obj != NULL) {
        fd1 = fd1Obj.getSocket();
        if (fd1 == null) {
            return;
        }
        fd1Obj.setSocket(null);
        NET_SocketClose(fd1);
    }
}

/*
 * check the addresses attached to the NetworkInterface object
 * and return the first one (of the requested family Ipv4 or Ipv6)
 * in *iaddr
 */

private static int getInetAddrFromIf (JNIEnv env, int family, NetworkInterface nif, InetAddress[] iaddr)
{
    InetAddress[] addrArray;
    int len;
    InetAddress addr;
    int i;

    addrArray = getNetworkInterfaceAddresses(nif);
    len = addrArray.length;

    /*
     * Check that there is at least one address bound to this
     * interface.
     */
    if (len < 1) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
            "bad argument for IP_MULTICAST_IF2: No IP addresses bound to interface");
        return -1;
    }
    for (i=0; i<len; i++) {
        int fam;
        addr = addrArray[i];
        fam = addr.family;
        if (fam == family) {
            iaddr[0] = addr;
            return 0;
        }
    }
    return -1;
}

private static int getInet4AddrFromIf (JNIEnv env, NetworkInterface nif, in_addr iaddr)
{
    InetAddress[] addr = new InetAddress[1];

    int ret = getInetAddrFromIf (env, IPv4, nif, addr);
    if (ret == -1) {
        return -1;
    }

    iaddr.s_addr = htonl(addr[0].address);
    return 0;
}

/* Get the multicasting index from the interface */

private static int getIndexFromIf (JNIEnv env, NetworkInterface nif) {
    return nif.getIndex();
}

private static InetAddress[] getNetworkInterfaceAddresses(final NetworkInterface nif) {
    // [IKVM] this is IKVM specific, because I don't want to use reflection (or map.xml hacks) to access the "addrs" member of NetworkInterface
    return java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<InetAddress[]>() {
        public InetAddress[] run() {
            java.util.ArrayList<InetAddress> list = new java.util.ArrayList<InetAddress>();
            for (java.util.Enumeration<InetAddress> e = nif.getInetAddresses(); e.hasMoreElements(); ) {
                list.add(e.nextElement());
            }
            return list.toArray(new InetAddress[list.size()]);
        }
    });
}

private static NetworkInterface Java_java_net_NetworkInterface_getByIndex(JNIEnv env, int ni_class, int index)
{
    try {
        return NetworkInterface.getByIndex(index);
    } catch (Exception x) {
        env.Throw(x);
        return null;
    }
}

private static NetworkInterface Java_java_net_NetworkInterface_getByInetAddress0(JNIEnv env, int ni_class, Object address)
{
    try {
        return NetworkInterface.getByInetAddress((InetAddress)address);
    } catch (Exception x) {
        env.Throw(x);
        return null;
    }
}

/*
 * Sets the multicast interface.
 *
 * SocketOptions.IP_MULTICAST_IF (argument is an InetAddress) :-
 *      IPv4:   set outgoing multicast interface using
 *              IPPROTO_IP/IP_MULTICAST_IF
 *
 *      IPv6:   Get the interface to which the
 *              InetAddress is bound
 *              and do same as SockOptions.IF_MULTICAST_IF2
 *
 * SockOptions.IF_MULTICAST_IF2 (argument is a NetworkInterface ) :-
 *      For each stack:
 *      IPv4:   Obtain IP address bound to network interface
 *              (NetworkInterface.addres[0])
 *              set outgoing multicast interface using
 *              IPPROTO_IP/IP_MULTICAST_IF
 *
 *      IPv6:   Obtain NetworkInterface.index
 *              Set outgoing multicast interface using
 *              IPPROTO_IPV6/IPV6_MULTICAST_IF
 *
 */
private static void setMulticastInterface(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, cli.System.Net.Sockets.Socket fd, cli.System.Net.Sockets.Socket fd1,
                                  int opt, Object value)
{
    boolean ipv6_supported = ipv6_available();

    if (opt == java_net_SocketOptions_IP_MULTICAST_IF) {
        /*
         * value is an InetAddress.
         * On IPv4 system use IP_MULTICAST_IF socket option
         * On IPv6 system get the NetworkInterface that this IP
         * address is bound to and use the IPV6_MULTICAST_IF
         * option instead of IP_MULTICAST_IF
         */
        if (ipv6_supported) {
            value = Java_java_net_NetworkInterface_getByInetAddress0(env, ni_class, value);
            if (value == NULL) {
                if (env.ExceptionOccurred() == null) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                         "bad argument for IP_MULTICAST_IF"
                        +": address not bound to any interface");
                }
                return;
            }
            opt = java_net_SocketOptions_IP_MULTICAST_IF2;
        } else {
            in_addr in = new in_addr();

            in.s_addr = htonl(((InetAddress)value).address);

            if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
                               in) < 0) {
                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                                 "Error setting socket option");
            }
            return;
        }
    }

    if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) {
        /*
         * value is a NetworkInterface.
         * On IPv6 system get the index of the interface and use the
         * IPV6_MULTICAST_IF socket option
         * On IPv4 system extract addr[0] and use the IP_MULTICAST_IF
         * option. For IPv6 both must be done.
         */
        if (ipv6_supported) {
            in_addr in = new in_addr();
            int index;

            index = ((NetworkInterface)value).getIndex();

            if (setsockopt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_IF,
                               index) < 0) {
                if (WSAGetLastError() == WSAEINVAL && index > 0) {
                    JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "IPV6_MULTICAST_IF failed (interface has IPv4 "
                       +"address only?)");
                } else {
                    NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                                   "Error setting socket option");
                }
                return;
            }

            /* If there are any IPv4 addresses on this interface then
             * repeat the operation on the IPv4 fd */

            if (getInet4AddrFromIf (env, (NetworkInterface)value, in) < 0) {
                return;
            }
            if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
                               in) < 0) {
                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                                 "Error setting socket option");
            }
            return;
        } else {
            in_addr in = new in_addr();

            if (getInet4AddrFromIf (env, (NetworkInterface)value, in) < 0) {
                if (env.ExceptionOccurred() != null) {
                    return;
                }
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "no InetAddress instances of requested type");
                return;
            }

            if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
                               in) < 0) {
                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                               "Error setting socket option");
            }
            return;
        }
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    socketSetOption
 * Signature: (ILjava/lang/Object;)V
 */
static void socketSetOption(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, int opt, Object value) {
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    int[] levelv4 = new int[1];
    int[] levelv6 = new int[1];
    int[] optnamev4 = new int[1];
    int[] optnamev6 = new int[1];
    Object optval;
    boolean ipv6_supported = ipv6_available();

    fd = getFD(env, _this);

    if (ipv6_supported) {
        fd1 = getFD1(env, _this);
    }
    if (fd == null && fd1 == null) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "socket closed");
        return;
    }

    if ((opt == java_net_SocketOptions_IP_MULTICAST_IF) ||
        (opt == java_net_SocketOptions_IP_MULTICAST_IF2)) {

        setMulticastInterface(env, _this, fd, fd1, opt, value);
        return;
    }

    /*
     * Map the Java level socket option to the platform specific
     * level(s) and option name(s).
     */
    if (fd1 != null) {
        if (NET_MapSocketOptionV6(opt, levelv6, optnamev6) != 0) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Invalid option");
            return;
        }
    }
    if (fd != null) {
        if (NET_MapSocketOption(opt, levelv4, optnamev4) != 0) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Invalid option");
            return;
        }
    }

    switch (opt) {
        case java_net_SocketOptions_SO_SNDBUF :
        case java_net_SocketOptions_SO_RCVBUF :
        case java_net_SocketOptions_IP_TOS :
            optval = ((Integer)value).intValue();
            break;

        case java_net_SocketOptions_SO_REUSEADDR:
        case java_net_SocketOptions_SO_BROADCAST:
        case java_net_SocketOptions_IP_MULTICAST_LOOP:
            {
                boolean on = ((Boolean)value).booleanValue();
                optval = on;
                /*
                 * setLoopbackMode (true) disables IP_MULTICAST_LOOP rather
                 * than enabling it.
                 */
                if (opt == java_net_SocketOptions_IP_MULTICAST_LOOP) {
                    optval = !on;
                }
            }
            break;

        default :
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                "Socket option not supported by PlainDatagramSocketImp");
            return;
    }

    if (fd1 != null) {
        if (NET_SetSockOpt(fd1, levelv6[0], optnamev6[0], optval) < 0) {
            NET_ThrowCurrent(env, "setsockopt IPv6");
            return;
        }
    }
    if (fd != null) {
        if (NET_SetSockOpt(fd, levelv4[0], optnamev4[0], optval) < 0) {
            NET_ThrowCurrent(env, "setsockopt");
            return;
        }
    }
}

/*
 * Return the multicast interface:
 *
 * SocketOptions.IP_MULTICAST_IF
 *      IPv4:   Query IPPROTO_IP/IP_MULTICAST_IF
 *              Create InetAddress
 *              IP_MULTICAST_IF returns struct ip_mreqn on 2.2
 *              kernel but struct in_addr on 2.4 kernel
 *      IPv6:   Query IPPROTO_IPV6 / IPV6_MULTICAST_IF or
 *              obtain from impl is Linux 2.2 kernel
 *              If index == 0 return InetAddress representing
 *              anyLocalAddress.
 *              If index > 0 query NetworkInterface by index
 *              and returns addrs[0]
 *
 * SocketOptions.IP_MULTICAST_IF2
 *      IPv4:   Query IPPROTO_IP/IP_MULTICAST_IF
 *              Query NetworkInterface by IP address and
 *              return the NetworkInterface that the address
 *              is bound too.
 *      IPv6:   Query IPPROTO_IPV6 / IPV6_MULTICAST_IF
 *              (except Linux .2 kernel)
 *              Query NetworkInterface by index and
 *              return NetworkInterface.
 */
private static Object getMulticastInterface(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, cli.System.Net.Sockets.Socket fd, cli.System.Net.Sockets.Socket fd1, int opt) {
    boolean isIPV4 = !ipv6_available() || fd1 == null;

    /*
     * IPv4 implementation
     */
    if (isIPV4) {
        Inet4Address addr;

        in_addr in = new in_addr();

        if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
                           in) < 0) {
            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                             "Error getting socket option");
            return NULL;
        }

        /*
         * Construct and populate an Inet4Address
         */
        addr = new Inet4Address();
        addr.address = ntohl(in.s_addr);

        /*
         * For IP_MULTICAST_IF return InetAddress
         */
        if (opt == java_net_SocketOptions_IP_MULTICAST_IF) {
            return addr;
        }

        NetworkInterface ni;
        ni = Java_java_net_NetworkInterface_getByInetAddress0(env, ni_class, addr);
        if (ni != null) {
            return ni;
        }

        /*
         * The address doesn't appear to be bound at any known
         * NetworkInterface. Therefore we construct a NetworkInterface
         * with this address.
         */
        return new NetworkInterface(null, -1, new InetAddress[] { addr });
    }


    /*
     * IPv6 implementation
     */
    if ((opt == java_net_SocketOptions_IP_MULTICAST_IF) ||
        (opt == java_net_SocketOptions_IP_MULTICAST_IF2)) {

        int index;

        InetAddress[] addrArray;
        InetAddress addr;
        NetworkInterface ni;

        {
            int[] tmp = { 0 };
            if (getsockopt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_IF,
                               tmp) < 0) {
                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                               "Error getting socket option");
                return NULL;
            }
            index = tmp[0];
        }

        /*
         * If multicast to a specific interface then return the
         * interface (for IF2) or the any address on that interface
         * (for IF).
         */
        if (index > 0) {
            ni = Java_java_net_NetworkInterface_getByIndex(env, ni_class,
                                                                   index);
            if (ni == NULL) {
                String errmsg = "IPV6_MULTICAST_IF returned index to unrecognized interface: " + index;
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", errmsg);
                return NULL;
            }

            /*
             * For IP_MULTICAST_IF2 return the NetworkInterface
             */
            if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) {
                return ni;
            }

            /*
             * For IP_MULTICAST_IF return addrs[0]
             */
            addrArray = getNetworkInterfaceAddresses(ni);
            if (addrArray.length < 1) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                    "IPV6_MULTICAST_IF returned interface without IP bindings");
                return NULL;
            }

            addr = addrArray[0];
            return addr;
        }

        /*
         * Multicast to any address - return anyLocalAddress
         * or a NetworkInterface with addrs[0] set to anyLocalAddress
         */

        addr = InetAddress.anyLocalAddress();
        if (opt == java_net_SocketOptions_IP_MULTICAST_IF) {
            return addr;
        }

        return new NetworkInterface(null, -1, new InetAddress[] { addr });
    }
    return NULL;
}
/*
 * Returns relevant info as a jint.
 *
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    socketGetOption
 * Signature: (I)Ljava/lang/Object;
 */
static Object socketGetOption(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, int opt) {
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    int[] level = new int[1];
    int[] optname = new int[1];
    int[] optval = new int[1];
    boolean ipv6_supported = ipv6_available();

    fd = getFD(env, _this);
    if (ipv6_supported) {
        fd1 = getFD1(env, _this);
    }

    if (fd == null && fd1 ==  null) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return NULL;
    }

    /*
     * Handle IP_MULTICAST_IF separately
     */
    if (opt == java_net_SocketOptions_IP_MULTICAST_IF ||
        opt == java_net_SocketOptions_IP_MULTICAST_IF2) {
        return getMulticastInterface(env, _this, fd, fd1, opt);
    }

    if (opt == java_net_SocketOptions_SO_BINDADDR) {
        /* find out local IP address */
        SOCKETADDRESS him;
        him = new SOCKETADDRESS();
        InetAddress iaObj;

        if (fd == null) {
            fd = fd1; /* must be IPv6 only */
        }

        if (getsockname(fd, him) == -1) {
            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG+"SocketException",
                           "Error getting socket name");
            return NULL;
        }
        iaObj = NET_SockaddrToInetAddress(him, new int[1]);

        return iaObj;
    }

    /*
     * Map the Java level socket option to the platform specific
     * level and option name.
     */
    if (NET_MapSocketOption(opt, level, optname) != 0) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Invalid option");
        return NULL;
    }

    if (fd == null) {
        if (NET_MapSocketOptionV6(opt, level, optname) != 0) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Invalid option");
            return NULL;
        }
        fd = fd1; /* must be IPv6 only */
    }

    if (NET_GetSockOpt(fd, level[0], optname[0], optval) < 0) {
        String errmsg = "error getting socket option: " + WSAGetLastError();
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", errmsg);
        return NULL;
    }

    switch (opt) {
        case java_net_SocketOptions_SO_BROADCAST:
        case java_net_SocketOptions_SO_REUSEADDR:
            return optval[0] != 0;

        case java_net_SocketOptions_IP_MULTICAST_LOOP:
            /* getLoopbackMode() returns true if IP_MULTICAST_LOOP is disabled */
            return optval[0] == 0;

        case java_net_SocketOptions_SO_SNDBUF:
        case java_net_SocketOptions_SO_RCVBUF:
        case java_net_SocketOptions_IP_TOS:
            return optval[0];

        default :
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                "Socket option not supported by TwoStacksPlainDatagramSocketImpl");
            return NULL;

    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    setTimeToLive
 * Signature: (I)V
 */
static void setTimeToLive(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, int ttl) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    } else {
      if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
      }
      if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
      }
    }

    /* setsockopt to be correct ttl */
    if (fd != null) {
      if (NET_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, ttl) < 0) {
        NET_ThrowCurrent(env, "set IP_MULTICAST_TTL failed");
      }
    }

    if (fd1 != null) {
      if (NET_SetSockOpt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl) <0) {
        NET_ThrowCurrent(env, "set IPV6_MULTICAST_HOPS failed");
      }
    }
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    setTTL
 * Signature: (B)V
 */
static void setTTL(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, byte ttl) {
    setTimeToLive(env, _this, ttl & 0xFF);
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    getTimeToLive
 * Signature: ()I
 */
static int getTimeToLive(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this) {
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;
    int[] ttl = new int[1];

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return -1;
    } else {
      if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
      }
      if (!IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
      }
    }

    /* getsockopt of ttl */
    if (fd != null) {
      if (NET_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, ttl) < 0) {
        NET_ThrowCurrent(env, "get IP_MULTICAST_TTL failed");
        return -1;
      }
      return ttl[0];
    }
    if (fd1 != null) {
      if (NET_GetSockOpt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl) < 0) {
        NET_ThrowCurrent(env, "get IP_MULTICAST_TTL failed");
        return -1;
      }
      return ttl[0];
    }
    return -1;
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    getTTL
 * Signature: ()B
 */
static byte getTTL(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this) {
    int result = getTimeToLive(env, _this);

    return (byte)result;
}

/* join/leave the named group on the named interface, or if no interface specified
 * then the interface set with setInterfac(), or the default interface otherwise */

private static void mcast_join_leave(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, InetAddress iaObj, NetworkInterface niObj, boolean join)
{
    FileDescriptor fdObj = _this.fd;
    FileDescriptor fd1Obj = _this.fd1;
    cli.System.Net.Sockets.Socket fd = null;
    cli.System.Net.Sockets.Socket fd1 = null;

    SOCKETADDRESS name;
    name = new SOCKETADDRESS();
    ip_mreq mname = new ip_mreq();
    ipv6_mreq mname6 = new ipv6_mreq();

    in_addr in = new in_addr();
    int ifindex;

    int family;
    boolean ipv6_supported = ipv6_available();
    int cmd ;

    if (IS_NULL(fdObj) && IS_NULL(fd1Obj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                        "Socket closed");
        return;
    }
    if (!IS_NULL(fdObj)) {
        fd = fdObj.getSocket();
    }
    if (ipv6_supported && !IS_NULL(fd1Obj)) {
        fd1 = fd1Obj.getSocket();
    }

    if (IS_NULL(iaObj)) {
        JNU_ThrowNullPointerException(env, "address");
        return;
    }

    if (NET_InetAddressToSockaddr(env, iaObj, 0, name, JNI_FALSE) != 0) {
      return;
    }

    /* Set the multicast group address in the ip_mreq field
     * eventually this check should be done by the security manager
     */
    family = name.him.sa_family;

    if (family == AF_INET) {
        int address = name.him4.sin_addr.s_addr;
        if (!IN_MULTICAST(ntohl(address))) {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "not in multicast");
            return;
        }
        mname.imr_multiaddr.s_addr = address;
        if (fd == null) {
          JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Can't join an IPv4 group on an IPv6 only socket");
          return;
        }
        if (IS_NULL(niObj)) {
            if (NET_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF, in) < 0) {
                NET_ThrowCurrent(env, "get IP_MULTICAST_IF failed");
                return;
            }
            mname.imr_interface.s_addr = in.s_addr;
        } else {
            if (getInet4AddrFromIf (env, niObj, mname.imr_interface) != 0) {
                NET_ThrowCurrent(env, "no Inet4Address associated with interface");
                return;
            }
        }

        cmd = join ? IP_ADD_MEMBERSHIP: IP_DROP_MEMBERSHIP;

        /* Join the multicast group */
        if (NET_SetSockOpt(fd, IPPROTO_IP, cmd, mname) < 0) {
            if (WSAGetLastError() == WSAENOBUFS) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                    "IP_ADD_MEMBERSHIP failed (out of hardware filters?)");
            } else {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException","error setting options");
            }
        }
    } else /* AF_INET6 */ {
        if (ipv6_supported) {
            in6_addr address;
            address = in6_addr.FromSockAddr(name);
            if (!IN6_IS_ADDR_MULTICAST(address)) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "not in6 multicast");
                return;
            }
            mname6.ipv6mr_multiaddr = address;
        } else {
            JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "IPv6 not supported");
            return;
        }
        if (fd1 == null) {
          JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException", "Can't join an IPv6 group on a IPv4 socket");
          return;
        }
        if (IS_NULL(niObj)) {
            int[] tmp = { 0 };
            if (NET_GetSockOpt(fd1, IPPROTO_IPV6, IPV6_MULTICAST_IF, tmp) < 0) {
                NET_ThrowCurrent(env, "get IPV6_MULTICAST_IF failed");
                return;
            }
            ifindex = tmp[0];
        } else {
            ifindex = getIndexFromIf (env, niObj);
            if (ifindex == -1) {
                NET_ThrowCurrent(env, "get ifindex failed");
                return;
            }
        }
        mname6.ipv6mr_interface = ifindex;
        cmd = join ? IPV6_ADD_MEMBERSHIP: IPV6_DROP_MEMBERSHIP;

        /* Join the multicast group */
        if (NET_SetSockOpt(fd1, IPPROTO_IPV6, cmd, mname6) < 0) {
            if (WSAGetLastError() == WSAENOBUFS) {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException",
                    "IP_ADD_MEMBERSHIP failed (out of hardware filters?)");
            } else {
                JNU_ThrowByName(env, JNU_JAVANETPKG+"SocketException","error setting options");
            }
        }
    }

    return;
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    join
 * Signature: (Ljava/net/InetAddress;)V
 */
static void join(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, InetAddress inetaddr, NetworkInterface netIf) {
    mcast_join_leave(env, _this, inetaddr, netIf, true);
}

/*
 * Class:     java_net_TwoStacksPlainDatagramSocketImpl
 * Method:    leave
 * Signature: (Ljava/net/InetAddress;)V
 */
static void leave(JNIEnv env, TwoStacksPlainDatagramSocketImpl _this, InetAddress inetaddr, NetworkInterface netIf) {
    mcast_join_leave(env, _this, inetaddr, netIf, false);
}

}
