Revert "Move PlainDatagramSocketImpl away from JNI"

The PlainDatagramSocketImpl rewrite change set caused a regression in
casting device discovery mechanism. Revert these changes while
investigating the root cause.

This reverts the following commits:
77110aac1821ca512c3d2b05f2f6b1f5ab7091f6  DatagramSocket should set IP_MULTICAST_ALL to 0
0c38d180ead8b6d9bf3268979ac0967cb7e29283  Move PlainDatagramSocketImpl away from JNI (part 3)
0cda0594094f792c4ff923ea55311e0bd51a37c1  Move PlainDatagramSocketImpl away from JNI (part 2)
5e0df13532a902abc72c267a92bf3d83e71b0b1c  Move PlainDatagramSocketImpl away from JNI (part 1)

Test: Manual testing
Bug: 33957878
Change-Id: I3d27e642ab77cb01ba6f1eb619847f67b47e44d8
(cherry picked from commit fba26224db3ed354a0b42c4bee5ed4ddc4d52f9a)
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 0c34d5e..fb348bc 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -35,7 +35,6 @@
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
 import java.net.PortUnreachableException;
-import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.SocketOptions;
 import java.net.SocketTimeoutException;
@@ -98,12 +97,7 @@
         try {
             Libcore.os.bind(fd, address, port);
         } catch (ErrnoException errnoException) {
-            if (errnoException.errno == EADDRINUSE || errnoException.errno == EADDRNOTAVAIL ||
-                errnoException.errno == EPERM || errnoException.errno == EACCES) {
-                throw new BindException(errnoException.getMessage(), errnoException);
-            } else {
-                throw new SocketException(errnoException.getMessage(), errnoException);
-            }
+            throw new BindException(errnoException.getMessage(), errnoException);
         }
     }
 
@@ -291,14 +285,15 @@
     private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws ErrnoException, SocketException {
         switch (option) {
         case SocketOptions.IP_MULTICAST_IF:
+            // This is IPv4-only.
+            return Libcore.os.getsockoptInAddr(fd, IPPROTO_IP, IP_MULTICAST_IF);
         case SocketOptions.IP_MULTICAST_IF2:
+            // This is IPv6-only.
             return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
         case SocketOptions.IP_MULTICAST_LOOP:
             // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
             // it doesn't matter which we return.
-            // NOTE: getsockopt's return value means "isEnabled", while OpenJDK code java.net
-            // requires a value that means "isDisabled" so we NEGATE the system call value here.
-            return !booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
         case IoBridge.JAVA_IP_MULTICAST_TTL:
             // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
             // it doesn't matter which we return.
@@ -333,8 +328,6 @@
             return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
         case SocketOptions.TCP_NODELAY:
             return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
-        case SocketOptions.SO_BINDADDR:
-            return ((InetSocketAddress) Libcore.os.getsockname(fd)).getAddress();
         default:
             throw new SocketException("Unknown socket option: " + option);
         }
@@ -363,15 +356,7 @@
     private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws ErrnoException, SocketException {
         switch (option) {
         case SocketOptions.IP_MULTICAST_IF:
-            NetworkInterface nif = NetworkInterface.getByInetAddress((InetAddress) value);
-            if (nif == null) {
-                throw new SocketException(
-                        "bad argument for IP_MULTICAST_IF : address not bound to any interface");
-            }
-            // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
-            Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, nif.getIndex());
-            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, nif.getIndex());
-            return;
+            throw new UnsupportedOperationException("Use IP_MULTICAST_IF2 on Android");
         case SocketOptions.IP_MULTICAST_IF2:
             // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
             Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
@@ -379,11 +364,8 @@
             return;
         case SocketOptions.IP_MULTICAST_LOOP:
             // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
-            // NOTE: setsockopt's arguement value means "isEnabled", while OpenJDK code java.net
-            // uses a value that means "isDisabled" so we NEGATE the system call value here.
-            int enable = booleanToInt(!((Boolean) value));
-            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, enable);
-            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, enable);
+            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, booleanToInt((Boolean) value));
+            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, booleanToInt((Boolean) value));
             return;
         case IoBridge.JAVA_IP_MULTICAST_TTL:
             // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
@@ -580,11 +562,10 @@
         return result;
     }
 
-    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException)
-            throws IOException {
+    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException {
         if (isDatagram) {
-            if (errnoException.errno == ECONNREFUSED) {
-                throw new PortUnreachableException("ICMP Port Unreachable");
+            if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) {
+                return 0;
             }
         } else {
             if (errnoException.errno == EAGAIN) {
@@ -593,7 +574,7 @@
                 return 0;
             }
         }
-        throw errnoException.rethrowAsIOException();
+        throw errnoException.rethrowAsSocketException();
     }
 
     public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
@@ -626,12 +607,8 @@
         }
         if (packet != null) {
             packet.setReceivedLength(byteCount);
+            packet.setAddress(srcAddress.getAddress());
             packet.setPort(srcAddress.getPort());
-
-            // packet.address should only be changed when it is different from srcAddress.
-            if (!srcAddress.getAddress().equals(packet.getAddress())) {
-                packet.setAddress(srcAddress.getAddress());
-            }
         }
         return byteCount;
     }
@@ -645,7 +622,7 @@
             }
         } else {
             if (isConnected && errnoException.errno == ECONNREFUSED) {
-                throw new PortUnreachableException("ICMP Port Unreachable", errnoException);
+                throw new PortUnreachableException("", errnoException);
             } else if (errnoException.errno == EAGAIN) {
                 throw new SocketTimeoutException(errnoException);
             } else {
diff --git a/ojluni/src/main/java/java/net/DatagramPacket.java b/ojluni/src/main/java/java/net/DatagramPacket.java
index fcdfb1c..1ce7cad 100644
--- a/ojluni/src/main/java/java/net/DatagramPacket.java
+++ b/ojluni/src/main/java/java/net/DatagramPacket.java
@@ -43,13 +43,12 @@
 public final
 class DatagramPacket {
 
-    // Android-removed: init method has been removed
-    // /**
-    //  * Perform class initialization
-    //  */
-    // static {
-    //     init();
-    // }
+    /**
+     * Perform class initialization
+     */
+    static {
+        init();
+    }
 
     /*
      * The fields of this class are package-private since DatagramSocketImpl
@@ -386,9 +385,8 @@
         this.bufLength = this.length;
     }
 
-    // Android-removed: JNI has been removed
-    // /**
-    //  * Perform class load-time initializations.
-    //  */
-    // private native static void init();
+    /**
+     * Perform class load-time initializations.
+     */
+    private native static void init();
 }
diff --git a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
index 09f7479..ea45500 100644
--- a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -24,26 +24,11 @@
  */
 package java.net;
 
-import android.system.ErrnoException;
-import android.system.StructGroupReq;
-
 import java.io.IOException;
-import libcore.io.IoBridge;
-import libcore.io.Libcore;
-import libcore.util.EmptyArray;
-
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
 import jdk.net.*;
-
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.AF_UNSPEC;
-import static android.system.OsConstants.IPPROTO_IP;
-import static android.system.OsConstants.IP_MULTICAST_ALL;
-import static android.system.OsConstants.MSG_PEEK;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static libcore.io.IoBridge.JAVA_IP_MULTICAST_TTL;
-import static libcore.io.IoBridge.JAVA_MCAST_JOIN_GROUP;
-import static libcore.io.IoBridge.JAVA_MCAST_LEAVE_GROUP;
 import static sun.net.ExtendedOptionsImpl.*;
 
 /*
@@ -54,10 +39,9 @@
 
 class PlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
 {
-    // Android-removed: init method has been removed
-    // static {
-    //     init();
-    // }
+    static {
+        init();
+    }
 
     protected <T> void setOption(SocketOption<T> name, T value) throws IOException {
         if (!name.equals(ExtendedSocketOptions.SO_FLOW_SLA)) {
@@ -94,160 +78,47 @@
         }
     }
 
-    protected synchronized void bind0(int lport, InetAddress laddr) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected synchronized native void bind0(int lport, InetAddress laddr)
+        throws SocketException;
 
-        IoBridge.bind(fd, laddr, lport);
+    protected native void send(DatagramPacket p) throws IOException;
 
-        if (lport == 0) {
-            // Now that we're a connected socket, let's extract the port number that the system
-            // chose for us and store it in the Socket object.
-            localPort = IoBridge.getLocalInetSocketAddress(fd).getPort();
-        } else {
-            localPort = lport;
-        }
-    }
+    protected synchronized native int peek(InetAddress i) throws IOException;
 
-    protected void send(DatagramPacket p) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-        if (p.getData() == null || p.getAddress() == null) {
-            throw new NullPointerException("null buffer || null address");
-        }
+    protected synchronized native int peekData(DatagramPacket p) throws IOException;
 
-        int port = connected ? 0 : p.getPort();
-        InetAddress address = connected ? null : p.getAddress();
-        IoBridge.sendto(fd, p.getData(), p.getOffset(), p.getLength(), 0, address, port);
-    }
+    protected synchronized native void receive0(DatagramPacket p)
+        throws IOException;
 
-    protected synchronized int peek(InetAddress i) throws IOException {
-        DatagramPacket p = new DatagramPacket(EmptyArray.BYTE, 0);
-        doRecv(p, MSG_PEEK);
-        i.holder().address = p.getAddress().holder().address;
-        return p.getPort();
-    }
+    protected native void setTimeToLive(int ttl) throws IOException;
 
-    protected synchronized int peekData(DatagramPacket p) throws IOException {
-        doRecv(p, MSG_PEEK);
-        return p.getPort();
-    }
+    protected native int getTimeToLive() throws IOException;
 
-    protected synchronized void receive0(DatagramPacket p) throws IOException {
-        doRecv(p, 0);
-    }
+    protected native void setTTL(byte ttl) throws IOException;
 
-    private void doRecv(DatagramPacket p, int flags) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected native byte getTTL() throws IOException;
 
-        if (timeout != 0) {
-            IoBridge.poll(fd, POLLIN, timeout);
-        }
+    protected native void join(InetAddress inetaddr, NetworkInterface netIf)
+        throws IOException;
 
-        IoBridge.recvfrom(false, fd, p.getData(), p.getOffset(), p.getLength(), flags, p,
-                connected);
-    }
+    protected native void leave(InetAddress inetaddr, NetworkInterface netIf)
+        throws IOException;
 
-    protected void setTimeToLive(int ttl) throws IOException {
-        IoBridge.setSocketOption(fd, JAVA_IP_MULTICAST_TTL, ttl);
-    }
+    protected native void datagramSocketCreate() throws SocketException;
 
-    protected int getTimeToLive() throws IOException {
-        return (Integer) IoBridge.getSocketOption(fd, JAVA_IP_MULTICAST_TTL);
-    }
+    protected native void datagramSocketClose();
 
-    protected void setTTL(byte ttl) throws IOException {
-        setTimeToLive((int) ttl & 0xff);
-    }
+    protected native void socketSetOption0(int opt, Object val)
+        throws SocketException;
 
-    protected byte getTTL() throws IOException {
-        return (byte) getTimeToLive();
-    }
+    protected native Object socketGetOption(int opt) throws SocketException;
 
-    private static StructGroupReq makeGroupReq(InetAddress gr_group,
-            NetworkInterface networkInterface) {
-        int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
-        return new StructGroupReq(gr_interface, gr_group);
-    }
+    protected native void connect0(InetAddress address, int port) throws SocketException;
 
-    protected void join(InetAddress inetaddr, NetworkInterface netIf) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected native void disconnect0(int family);
 
-        IoBridge.setSocketOption(fd, JAVA_MCAST_JOIN_GROUP, makeGroupReq(inetaddr, netIf));
-    }
-
-    protected void leave(InetAddress inetaddr, NetworkInterface netIf)
-        throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        IoBridge.setSocketOption(fd, JAVA_MCAST_LEAVE_GROUP, makeGroupReq(inetaddr, netIf));
-    }
-
-    protected void datagramSocketCreate() throws SocketException {
-        fd = IoBridge.socket(AF_INET6, SOCK_DGRAM, 0);
-        IoBridge.setSocketOption(fd, SO_BROADCAST, true);
-
-        try {
-            Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_MULTICAST_ALL, 0);
-        } catch (ErrnoException errnoException) {
-            throw errnoException.rethrowAsSocketException();
-        }
-    }
-
-    protected void datagramSocketClose() {
-        try {
-            IoBridge.closeAndSignalBlockedThreads(fd);
-        } catch (IOException ignored) { }
-    }
-
-    protected void socketSetOption0(int opt, Object val) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        IoBridge.setSocketOption(fd, opt, val);
-    }
-
-    protected Object socketGetOption(int opt) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        return IoBridge.getSocketOption(fd, opt);
-    }
-
-    protected void connect0(InetAddress address, int port) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        IoBridge.connect(fd, address, port);
-    }
-
-    protected void disconnect0(int family) {
-        if (isClosed()) {
-            return;
-        }
-
-        InetAddress inetAddressUnspec = new InetAddress();
-        inetAddressUnspec.holder().family = AF_UNSPEC;
-
-        try {
-            IoBridge.connect(fd, inetAddressUnspec, 0);
-        } catch (SocketException ignored) { }
-    }
-
-    // Android-removed: JNI has been removed
-    // /**
-    //  * Perform class load-time initializations.
-    //  */
-    // private native static void init();
+    /**
+     * Perform class load-time initializations.
+     */
+    private native static void init();
 }
diff --git a/ojluni/src/main/native/DatagramPacket.c b/ojluni/src/main/native/DatagramPacket.c
new file mode 100644
index 0000000..fac60ed
--- /dev/null
+++ b/ojluni/src/main/native/DatagramPacket.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1997, 2002, 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.
+ */
+
+#include "net_util.h"
+#include "JNIHelp.h"
+
+#define NATIVE_METHOD(className, functionName, signature) \
+{ #functionName, signature, (void*)(className ## _ ## functionName) }
+
+/************************************************************************
+ * DatagramPacket
+ */
+
+jfieldID dp_addressID;
+jfieldID dp_portID;
+jfieldID dp_bufID;
+jfieldID dp_offsetID;
+jfieldID dp_lengthID;
+jfieldID dp_bufLengthID;
+
+/*
+ * Class:     java_net_DatagramPacket
+ * Method:    init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+DatagramPacket_init (JNIEnv *env, jclass cls) {
+    dp_addressID = (*env)->GetFieldID(env, cls, "address",
+                                      "Ljava/net/InetAddress;");
+    CHECK_NULL(dp_addressID);
+    dp_portID = (*env)->GetFieldID(env, cls, "port", "I");
+    CHECK_NULL(dp_portID);
+    dp_bufID = (*env)->GetFieldID(env, cls, "buf", "[B");
+    CHECK_NULL(dp_bufID);
+    dp_offsetID = (*env)->GetFieldID(env, cls, "offset", "I");
+    CHECK_NULL(dp_offsetID);
+    dp_lengthID = (*env)->GetFieldID(env, cls, "length", "I");
+    CHECK_NULL(dp_lengthID);
+    dp_bufLengthID = (*env)->GetFieldID(env, cls, "bufLength", "I");
+    CHECK_NULL(dp_bufLengthID);
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(DatagramPacket, init, "()V"),
+};
+
+void register_java_net_DatagramPacket(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "java/net/DatagramPacket", gMethods, NELEM(gMethods));
+}
diff --git a/ojluni/src/main/native/PlainDatagramSocketImpl.c b/ojluni/src/main/native/PlainDatagramSocketImpl.c
new file mode 100644
index 0000000..b36e286
--- /dev/null
+++ b/ojluni/src/main/native/PlainDatagramSocketImpl.c
@@ -0,0 +1,1965 @@
+/*
+ * Copyright (c) 1997, 2011, 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.
+ */
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifdef __solaris__
+#include <fcntl.h>
+#endif
+#ifdef __linux__
+#include <unistd.h>
+//#include <sys/sysctl.h>
+#include <sys/utsname.h>
+#include <netinet/ip.h>
+
+#define IPV6_MULTICAST_IF 17
+#ifndef SO_BSDCOMPAT
+#define SO_BSDCOMPAT  14
+#endif
+/**
+ * IP_MULTICAST_ALL has been supported since kernel version 2.6.31
+ * but we may be building on a machine that is older than that.
+ */
+#ifndef IP_MULTICAST_ALL
+#define IP_MULTICAST_ALL      49
+#endif
+#endif  //  __linux__
+
+#ifndef IPTOS_TOS_MASK
+#define IPTOS_TOS_MASK 0x1e
+#endif
+#ifndef IPTOS_PREC_MASK
+#define IPTOS_PREC_MASK 0xe0
+#endif
+
+#include "jvm.h"
+#include "jni_util.h"
+#include "net_util.h"
+
+#include "java_net_SocketOptions.h"
+#include "java_net_PlainDatagramSocketImpl.h"
+#include "JNIHelp.h"
+
+#define NATIVE_METHOD(className, functionName, signature) \
+{ #functionName, signature, (void*)(className ## _ ## functionName) }
+/************************************************************************
+ * PlainDatagramSocketImpl
+ */
+
+static jfieldID IO_fd_fdID;
+
+static jfieldID pdsi_fdID;
+static jfieldID pdsi_timeoutID;
+static jfieldID pdsi_trafficClassID;
+static jfieldID pdsi_localPortID;
+static jfieldID pdsi_connected;
+static jfieldID pdsi_connectedAddress;
+static jfieldID pdsi_connectedPort;
+
+extern void setDefaultScopeID(JNIEnv *env, struct sockaddr *him);
+extern int getDefaultScopeID(JNIEnv *env);
+
+/*
+ * Returns a java.lang.Integer based on 'i'
+ */
+static jobject createInteger(JNIEnv *env, int i) {
+    static jclass i_class;
+    static jmethodID i_ctrID;
+
+    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'
+ */
+static jobject createBoolean(JNIEnv *env, int b) {
+    static jclass b_class;
+    static jmethodID b_ctrID;
+
+    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)) );
+}
+
+
+/*
+ * Returns the fd for a PlainDatagramSocketImpl or -1
+ * if closed.
+ */
+static int getFD(JNIEnv *env, jobject this) {
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    if (fdObj == NULL) {
+        return -1;
+    }
+    return (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+}
+
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_init(JNIEnv *env, jclass cls) {
+
+#ifdef __linux__
+    struct utsname sysinfo;
+#endif
+    pdsi_fdID = (*env)->GetFieldID(env, cls, "fd",
+                                   "Ljava/io/FileDescriptor;");
+    CHECK_NULL(pdsi_fdID);
+    pdsi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I");
+    CHECK_NULL(pdsi_timeoutID);
+    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);
+    pdsi_connectedAddress = (*env)->GetFieldID(env, cls, "connectedAddress",
+                                               "Ljava/net/InetAddress;");
+    CHECK_NULL(pdsi_connectedAddress);
+    pdsi_connectedPort = (*env)->GetFieldID(env, cls, "connectedPort", "I");
+    CHECK_NULL(pdsi_connectedPort);
+
+    IO_fd_fdID = NET_GetFileDescriptorID(env);
+    CHECK_NULL(IO_fd_fdID);
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    bind
+ * Signature: (ILjava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_bind0(JNIEnv *env, jobject this,
+                                           jint localport, jobject iaObj) {
+    /* fdObj is the FileDescriptor field on this */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* fd is an int field on fdObj */
+    int fd;
+    int len = 0;
+    SOCKADDR him;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+
+    if (IS_NULL(iaObj)) {
+        JNU_ThrowNullPointerException(env, "iaObj is null.");
+        return;
+    }
+
+    /* bind */
+    if (NET_InetAddressToSockaddr(env, iaObj, localport, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
+      return;
+    }
+    setDefaultScopeID(env, (struct sockaddr *)&him);
+
+    if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0)  {
+        if (errno == EADDRINUSE || errno == EADDRNOTAVAIL ||
+            errno == EPERM || errno == EACCES) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException",
+                            "Bind failed");
+        } else {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                            "Bind failed");
+        }
+        return;
+    }
+
+    /* initialize the local port */
+    if (localport == 0) {
+        /* Now that we're a connected socket, let's extract the port number
+         * that the system chose for us and store it in the Socket object.
+         */
+        if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                            "Error getting socket name");
+            return;
+        }
+
+        localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
+
+        (*env)->SetIntField(env, this, pdsi_localPortID, localport);
+    } else {
+        (*env)->SetIntField(env, this, pdsi_localPortID, localport);
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    connect0
+ * Signature: (Ljava/net/InetAddress;I)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_connect0(JNIEnv *env, jobject this,
+                                               jobject address, jint port) {
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* The fdObj'fd */
+    jint fd;
+    /* The packetAddress address, family and port */
+    SOCKADDR rmtaddr;
+    int len = 0;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(address)) {
+        JNU_ThrowNullPointerException(env, "address");
+        return;
+    }
+
+    if (NET_InetAddressToSockaddr(env, address, port, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+      return;
+    }
+
+    setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+    if (JVM_Connect(fd, (struct sockaddr *)&rmtaddr, len) == -1) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
+                        "Connect failed");
+        return;
+    }
+
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    disconnect0
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_disconnect0(JNIEnv *env, jobject this, jint family) {
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* The fdObj'fd */
+    jint fd;
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+    SOCKADDR addr;
+    int len;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+        memset(&addr, 0, sizeof(addr));
+#ifdef AF_INET6
+        if (ipv6_available()) {
+            struct sockaddr_in6 *him6 = (struct sockaddr_in6 *)&addr;
+            him6->sin6_family = AF_UNSPEC;
+            len = sizeof(struct sockaddr_in6);
+        } else
+#endif
+        {
+            struct sockaddr_in *him4 = (struct sockaddr_in*)&addr;
+            him4->sin_family = AF_UNSPEC;
+            len = sizeof(struct sockaddr_in);
+        }
+        JVM_Connect(fd, (struct sockaddr *)&addr, len);
+
+#ifdef __linux__
+        int localPort = 0;
+        if (JVM_GetSockName(fd, (struct sockaddr *)&addr, &len) == -1)
+            return;
+        localPort = NET_GetPortFromSockaddr((struct sockaddr *)&addr);
+        if (localPort == 0) {
+            localPort = (*env)->GetIntField(env, this, pdsi_localPortID);
+#ifdef AF_INET6
+            if (((struct sockaddr*)&addr)->sa_family == AF_INET6) {
+                ((struct sockaddr_in6*)&addr)->sin6_port = htons(localPort);
+            } else
+#endif /* AF_INET6 */
+            {
+                ((struct sockaddr_in*)&addr)->sin_port = htons(localPort);
+            }
+            NET_Bind(fd, (struct sockaddr *)&addr, len);
+        }
+#endif
+#else
+    JVM_Connect(fd, 0, 0);
+#endif
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    send
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_send(JNIEnv *env, jobject this,
+                                           jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int ret, mallocedPacket = JNI_FALSE;
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint trafficClass = (*env)->GetIntField(env, this, pdsi_trafficClassID);
+
+    jbyteArray packetBuffer;
+    jobject packetAddress;
+    jint packetBufferOffset, packetBufferLen, packetPort;
+    jboolean connected;
+
+    /* The fdObj'fd */
+    jint fd;
+
+    SOCKADDR rmtaddr, *rmtaddrP=&rmtaddr;
+    int len;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return;
+    }
+
+    connected = (*env)->GetBooleanField(env, this, pdsi_connected);
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+    if (IS_NULL(packetBuffer) || IS_NULL(packetAddress)) {
+        JNU_ThrowNullPointerException(env, "null buffer || null address");
+        return;
+    }
+
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_lengthID);
+
+    if (connected) {
+        /* arg to NET_Sendto () null in this case */
+        len = 0;
+        rmtaddrP = 0;
+    } else {
+        packetPort = (*env)->GetIntField(env, packet, dp_portID);
+        if (NET_InetAddressToSockaddr(env, packetAddress, packetPort, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+          return;
+        }
+    }
+    setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes 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* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * Anything bigger should be truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Send buffer native heap allocation failed");
+            return;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen,
+                               (jbyte *)fullPacket);
+#ifdef AF_INET6
+    if (trafficClass != 0 && ipv6_available()) {
+        NET_SetTrafficClass((struct sockaddr *)&rmtaddr, trafficClass);
+    }
+#endif /* AF_INET6 */
+
+
+    /*
+     * Send the datagram.
+     *
+     * If we are connected it's possible that sendto will return
+     * ECONNREFUSED indicating that an ICMP port unreachable has
+     * received.
+     */
+    ret = NET_SendTo(fd, fullPacket, packetBufferLen, 0,
+                     (struct sockaddr *)rmtaddrP, len);
+
+    if (ret < 0) {
+        switch (ret) {
+            case JVM_IO_ERR :
+                if (errno == ECONNREFUSED) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+                } else {
+                    NET_ThrowByNameWithLastError(env, "java/io/IOException", "sendto failed");
+                }
+                break;
+
+            case JVM_IO_INTR:
+                JNU_ThrowByName(env, "java/io/InterruptedIOException",
+                                "operation interrupted");
+                break;
+        }
+    }
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+    return;
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    peek
+ * Signature: (Ljava/net/InetAddress;)I
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peek(JNIEnv *env, jobject this,
+                                           jobject addressObj) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+    jint fd;
+    ssize_t n;
+    SOCKADDR remote_addr;
+    int len;
+    char buf[1];
+    jint family;
+    jobject iaObj;
+    int port;
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+        return -1;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    if (IS_NULL(addressObj)) {
+        JNU_ThrowNullPointerException(env, "Null address in peek()");
+        return -1;
+    }
+    if (timeout) {
+        int 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) {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+            }
+            return ret;
+        } else if (ret == JVM_IO_INTR) {
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+            return ret; /* WARNING: SHOULD WE REALLY RETURN -2??? */
+        }
+    }
+
+    len = SOCKADDR_LEN;
+    n = NET_RecvFrom(fd, buf, 1, MSG_PEEK,
+                     (struct sockaddr *)&remote_addr, &len);
+
+    if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+        if (errno == ECONNREFUSED) {
+            int orig_errno = errno;
+            (void) recv(fd, buf, 1, 0);
+            errno = orig_errno;
+        }
+#endif
+        if (errno == ECONNREFUSED) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+        } else {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+            }
+        }
+        return 0;
+    } else if (n == JVM_IO_INTR) {
+        JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
+        return 0;
+    }
+
+    iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+#ifdef AF_INET6
+    family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+#else
+    family = AF_INET;
+#endif
+    if (family == AF_INET) { /* this API can't handle IPV6 addresses */
+        int address = getInetAddress_addr(env, iaObj);
+        setInetAddress_addr(env, addressObj, address);
+    }
+    return port;
+}
+
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peekData(JNIEnv *env, jobject this,
+                                           jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int mallocedPacket = JNI_FALSE;
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+    jbyteArray packetBuffer;
+    jint packetBufferOffset, packetBufferLen;
+
+    int fd;
+
+    int n;
+    SOCKADDR remote_addr;
+    int len;
+    int port;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return -1;
+    }
+
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return -1;
+    }
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    if (IS_NULL(packetBuffer)) {
+        JNU_ThrowNullPointerException(env, "packet buffer");
+        return -1;
+    }
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+    if (timeout) {
+        int ret = NET_Timeout(fd, timeout);
+        if (ret == 0) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                            "Receive timed out");
+            return -1;
+        } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+            if (errno == EBADF) {
+                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+            }
+#else
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+            return -1;
+        } else if (ret == JVM_IO_INTR) {
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+            return -1;
+        }
+    }
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes 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* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * anything bigger is truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Peek buffer native heap allocation failed");
+            return -1;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    len = SOCKADDR_LEN;
+    n = NET_RecvFrom(fd, fullPacket, packetBufferLen, MSG_PEEK,
+                     (struct sockaddr *)&remote_addr, &len);
+    /* truncate the data if the packet's length is too small */
+    if (n > packetBufferLen) {
+        n = packetBufferLen;
+    }
+    if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+        if (errno == ECONNREFUSED) {
+            int orig_errno = errno;
+            (void) recv(fd, fullPacket, 1, 0);
+            errno = orig_errno;
+        }
+#endif
+        (*env)->SetIntField(env, packet, dp_offsetID, 0);
+        (*env)->SetIntField(env, packet, dp_lengthID, 0);
+        if (errno == ECONNREFUSED) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+        } else {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+            }
+        }
+    } else if (n == JVM_IO_INTR) {
+        (*env)->SetIntField(env, packet, dp_offsetID, 0);
+        (*env)->SetIntField(env, packet, dp_lengthID, 0);
+        JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                        "operation interrupted");
+    } else {
+        /*
+         * success - fill in received address...
+         *
+         * REMIND: Fill in an int on the packet, and create inetadd
+         * object in Java, as a performance improvement. Also
+         * construct the inetadd object lazily.
+         */
+
+        jobject 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 = (*env)->GetObjectField(env, packet, dp_addressID);
+        if (packetAddress != NULL) {
+            if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+                /* force a new InetAddress to be created */
+                packetAddress = NULL;
+            }
+        }
+        if (packetAddress == NULL) {
+            packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+            /* stuff the new Inetaddress in the packet */
+            (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+        } else {
+            /* only get the new port number */
+            port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+        }
+        /* and fill in the data, remote address/port and such */
+        (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+                                   (jbyte *)fullPacket);
+        (*env)->SetIntField(env, packet, dp_portID, port);
+        (*env)->SetIntField(env, packet, dp_lengthID, n);
+    }
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+    return port;
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    receive
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_receive0(JNIEnv *env, jobject this,
+                                              jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int mallocedPacket = JNI_FALSE;
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+    jbyteArray packetBuffer;
+    jint packetBufferOffset, packetBufferLen;
+
+    int fd;
+
+    int n;
+    SOCKADDR remote_addr;
+    int len;
+    jboolean retry;
+#ifdef __linux__
+    jboolean connected = JNI_FALSE;
+    jobject connectedAddress = NULL;
+    jint connectedPort = 0;
+    jlong prevTime = 0;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return;
+    }
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    if (IS_NULL(packetBuffer)) {
+        JNU_ThrowNullPointerException(env, "packet buffer");
+        return;
+    }
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes 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* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * anything bigger is truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Receive buffer native heap allocation failed");
+            return;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+
+    do {
+        retry = JNI_FALSE;
+
+        if (timeout) {
+            int ret = NET_Timeout(fd, timeout);
+            if (ret <= 0) {
+                if (ret == 0) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                                    "Receive timed out");
+                } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+                    if (errno == EBADF) {
+                         JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+                     } else {
+                         NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+                     }
+#else
+                     JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+                } else if (ret == JVM_IO_INTR) {
+                    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                                    "operation interrupted");
+                }
+
+                if (mallocedPacket) {
+                    free(fullPacket);
+                }
+
+                return;
+            }
+        }
+
+        len = SOCKADDR_LEN;
+        n = NET_RecvFrom(fd, fullPacket, packetBufferLen, 0,
+                         (struct sockaddr *)&remote_addr, &len);
+        /* truncate the data if the packet's length is too small */
+        if (n > packetBufferLen) {
+            n = packetBufferLen;
+        }
+        if (n == JVM_IO_ERR) {
+            (*env)->SetIntField(env, packet, dp_offsetID, 0);
+            (*env)->SetIntField(env, packet, dp_lengthID, 0);
+            if (errno == ECONNREFUSED) {
+                JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                                "ICMP Port Unreachable");
+            } else {
+                if (errno == EBADF) {
+                     JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+                 } else {
+                     NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+                 }
+            }
+        } else if (n == JVM_IO_INTR) {
+            (*env)->SetIntField(env, packet, dp_offsetID, 0);
+            (*env)->SetIntField(env, packet, dp_lengthID, 0);
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+        } else {
+            int port;
+            jobject packetAddress;
+
+            /*
+             * success - fill in received address...
+             *
+             * REMIND: Fill in an int on the packet, and create inetadd
+             * object in Java, as a performance improvement. Also
+             * construct the inetadd object lazily.
+             */
+
+            /*
+             * 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 = (*env)->GetObjectField(env, packet, dp_addressID);
+            if (packetAddress != NULL) {
+                if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+                    /* force a new InetAddress to be created */
+                    packetAddress = NULL;
+                }
+            }
+            if (packetAddress == NULL) {
+                packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+                /* stuff the new Inetaddress in the packet */
+                (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+            } else {
+                /* only get the new port number */
+                port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+            }
+            /* and fill in the data, remote address/port and such */
+            (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+                                       (jbyte *)fullPacket);
+            (*env)->SetIntField(env, packet, dp_portID, port);
+            (*env)->SetIntField(env, packet, dp_lengthID, n);
+        }
+
+    } while (retry);
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    datagramSocketCreate
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env,
+                                                           jobject this) {
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int arg, fd, t = 1;
+#ifdef AF_INET6
+    int domain = ipv6_available() ? AF_INET6 : AF_INET;
+#else
+    int domain = AF_INET;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+
+    if ((fd = JVM_Socket(domain, SOCK_DGRAM, 0)) == JVM_IO_ERR) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error creating socket");
+        return;
+    }
+    tagSocket(env, fd);
+
+#ifdef AF_INET6
+    /* Disable IPV6_V6ONLY to ensure dual-socket support */
+    if (domain == AF_INET6) {
+        arg = 0;
+        if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg,
+                       sizeof(int)) < 0) {
+            NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6");
+            untagSocket(env, fd);
+            close(fd);
+            return;
+        }
+    }
+#endif /* AF_INET6 */
+
+#ifdef __APPLE__
+    arg = 65507;
+    if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
+                       (char *)&arg, sizeof(arg)) < 0) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        strerror(errno));
+        return;
+    }
+    if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
+                       (char *)&arg, sizeof(arg)) < 0) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        strerror(errno));
+        return;
+    }
+#endif /* __APPLE__ */
+
+     setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof(int));
+
+#if defined(__linux__)
+     arg = 0;
+     int level = (domain == AF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP;
+     if ((setsockopt(fd, level, IP_MULTICAST_ALL, (char*)&arg, sizeof(arg)) < 0) &&
+         (errno != ENOPROTOOPT)) {
+         JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                         strerror(errno));
+         close(fd);
+         return;
+     }
+#endif
+
+#if defined (__linux__) && defined (AF_INET6)
+    /*
+     * On Linux for IPv6 sockets we must set the hop limit
+     * to 1 to be compatible with default TTL of 1 for IPv4 sockets.
+     */
+    if (domain == AF_INET6) {
+        int ttl = 1;
+        setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl,
+                   sizeof(ttl));
+    }
+#endif /* __linux__ */
+
+    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    datagramSocketClose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketClose(JNIEnv *env,
+                                                          jobject this) {
+    /*
+     * REMIND: PUT A LOCK AROUND THIS CODE
+     */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int fd;
+
+    if (IS_NULL(fdObj)) {
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    if (fd == -1) {
+        return;
+    }
+    (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
+    untagSocket(env, fd);
+    NET_SocketClose(fd);
+}
+
+
+/*
+ * Set outgoing multicast interface designated by a NetworkInterface index.
+ * Throw exception if failed.
+ *
+ * Android changed: return 0 on success, negative on failure.
+ * Android changed: Interface index (not NetworkInterface) as the parameter
+ */
+static int mcast_set_if_by_if_v4(JNIEnv *env, jobject this, int fd, jint ifindex) {
+    struct ip_mreqn req;
+    memset(&req, 0, sizeof(req));
+    req.imr_ifindex = ifindex;
+
+    if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF,
+                       (const char*)&req, sizeof(req)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error setting socket option");
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Set outgoing multicast interface designated by a NetworkInterface.
+ * Throw exception if failed.
+ * Android changed: Interface index (not NetworkInterface) as the parameter
+ */
+static void mcast_set_if_by_if_v6(JNIEnv *env, jobject this, int fd, jint ifindex) {
+    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                       (const char*)&ifindex, sizeof(ifindex)) < 0) {
+        if (errno == EINVAL && ifindex > 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;
+    }
+}
+
+/*
+ * Set outgoing multicast interface designated by an InetAddress.
+ * Throw exception if failed.
+ *
+ * Android-changed : Return type, return 0 on success, negative on failure.
+ */
+static int mcast_set_if_by_addr_v4(JNIEnv *env, jobject this, int fd, jobject value) {
+    struct in_addr in;
+
+    in.s_addr = htonl( getInetAddress_addr(env, value) );
+
+    if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_IF,
+                       (const char*)&in, sizeof(in)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                         "Error setting socket option");
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Set outgoing multicast interface designated by an InetAddress.
+ * Throw exception if failed.
+ */
+static void mcast_set_if_by_addr_v6(JNIEnv *env, jobject this, int fd, jobject value) {
+    static jclass ni_class;
+    static jmethodID ni_getByInetAddress;
+    static jmethodID ni_getIndex;
+    if (ni_class == NULL) {
+        jclass c = (*env)->FindClass(env, "java/net/NetworkInterface");
+        CHECK_NULL(c);
+        ni_class = (*env)->NewGlobalRef(env, c);
+        CHECK_NULL(ni_class);
+        ni_getByInetAddress = (*env)->GetStaticMethodID(
+            env, ni_class, "getByInetAddress", "(Ljava/net/InetAddress;)Ljava/net/NetworkInterface;");
+        CHECK_NULL(ni_getByInetAddress);
+        ni_getIndex = (*env)->GetMethodID(
+            env, ni_class, "getIndex", "()I");
+        CHECK_NULL(ni_getIndex);
+    }
+
+    /*
+     * Get the NetworkInterface by inetAddress
+     */
+    jobject ni_value = (*env)->CallStaticObjectMethod(
+        env, ni_class, ni_getByInetAddress, value);
+    if (ni_value == NULL) {
+        if (!(*env)->ExceptionOccurred(env)) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                 "bad argument for IP_MULTICAST_IF"
+                 ": address not bound to any interface");
+        }
+        return;
+    }
+
+    /*
+     * Get the NetworkInterface index
+     */
+    jint ifindex = (*env)->CallIntMethod(env, ni_value, ni_getIndex);
+    if ((*env)->ExceptionOccurred(env)) {
+        return;
+    }
+
+    mcast_set_if_by_if_v6(env, this, fd, ifindex);
+}
+
+/*
+ * Sets the multicast interface.
+ *
+ * SocketOptions.IP_MULTICAST_IF :-
+ *      value is a InetAddress
+ *      IPv4:   set outgoing multicast interface using
+ *              IPPROTO_IP/IP_MULTICAST_IF
+ *      IPv6:   Get the index of the interface to which the
+ *              InetAddress is bound
+ *              Set outgoing multicast interface using
+ *              IPPROTO_IPV6/IPV6_MULTICAST_IF
+ *
+ * SockOptions.IF_MULTICAST_IF2 :-
+ *      value is a NetworkInterface
+ *      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
+ *
+ */
+static void setMulticastInterface(JNIEnv *env, jobject this, int fd,
+                                  jint opt, jobject value)
+{
+    if (opt == java_net_SocketOptions_IP_MULTICAST_IF) {
+        /*
+         * value is an InetAddress.
+         */
+        // Android-changed: Return early if mcast_set_if_by_addr_v4 threw.
+        // We don't want to call into the IPV6 code with a pending exception.
+        if (mcast_set_if_by_addr_v4(env, this, fd, value)) {
+            return;
+        }
+        if (ipv6_available()) {
+            mcast_set_if_by_addr_v6(env, this, fd, value);
+        }
+    }
+
+    if (opt == java_net_SocketOptions_IP_MULTICAST_IF2) {
+      /*
+         * value is a Integer (Android-changed, openJdk uses NetworkInterface)
+         */
+        static jfieldID integer_valueID;
+        if (integer_valueID == NULL) {
+            jclass c = (*env)->FindClass(env, "java/lang/Integer");
+            CHECK_NULL(c);
+            integer_valueID = (*env)->GetFieldID(env, c, "value", "I");
+            CHECK_NULL(integer_valueID);
+        }
+        int index = (*env)->GetIntField(env, value, integer_valueID);
+
+        // Android-changed: Return early if mcast_set_if_by_addr_v4 threw.
+        // We don't want to call into the IPV6 code with a pending exception.
+        if (mcast_set_if_by_if_v4(env, this, fd, index)) {
+            return;
+        }
+        if (ipv6_available()) {
+            mcast_set_if_by_if_v6(env, this, fd, index);
+        }
+    }
+}
+
+/*
+ * Enable/disable local loopback of multicast datagrams.
+ */
+static void mcast_set_loop_v4(JNIEnv *env, jobject this, int fd, jobject value) {
+    jclass cls;
+    jfieldID fid;
+    jboolean on;
+    char loopback;
+
+    cls = (*env)->FindClass(env, "java/lang/Boolean");
+    CHECK_NULL(cls);
+    fid =  (*env)->GetFieldID(env, cls, "value", "Z");
+    CHECK_NULL(fid);
+
+    on = (*env)->GetBooleanField(env, value, fid);
+    loopback = (!on ? 1 : 0);
+
+    if (NET_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void *)&loopback, sizeof(char)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option");
+        return;
+    }
+}
+
+/*
+ * Enable/disable local loopback of multicast datagrams.
+ */
+#ifdef AF_INET6
+static void mcast_set_loop_v6(JNIEnv *env, jobject this, int fd, jobject value) {
+    jclass cls;
+    jfieldID fid;
+    jboolean on;
+    int loopback;
+
+    cls = (*env)->FindClass(env, "java/lang/Boolean");
+    CHECK_NULL(cls);
+    fid =  (*env)->GetFieldID(env, cls, "value", "Z");
+    CHECK_NULL(fid);
+
+    on = (*env)->GetBooleanField(env, value, fid);
+    loopback = (!on ? 1 : 0);
+
+    if (NET_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const void *)&loopback, sizeof(int)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option");
+        return;
+    }
+
+}
+#endif  /* AF_INET6 */
+
+/*
+ * Sets the multicast loopback mode.
+ */
+static void setMulticastLoopbackMode(JNIEnv *env, jobject this, int fd,
+                                  jint opt, jobject value) {
+#ifdef AF_INET6
+#ifdef __linux__
+    mcast_set_loop_v4(env, this, fd, value);
+    if (ipv6_available()) {
+        mcast_set_loop_v6(env, this, fd, value);
+    }
+#else  /* __linux__ not defined */
+    if (ipv6_available()) {
+        mcast_set_loop_v6(env, this, fd, value);
+    } else {
+        mcast_set_loop_v4(env, this, fd, value);
+    }
+#endif  /* __linux__ */
+#else
+    mcast_set_loop_v4(env, this, fd, value);
+#endif  /* AF_INET6 */
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    socketSetOption0
+ * Signature: (ILjava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_socketSetOption0(JNIEnv *env,
+                                                      jobject this,
+                                                      jint opt,
+                                                      jobject value) {
+    int fd;
+    int level, optname, optlen;
+    union {
+        int i;
+        char c;
+    } optval;
+
+    /*
+     * Check that socket hasn't been closed
+     */
+    fd = getFD(env, this);
+    if (fd < 0) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+
+    /*
+     * Check argument has been provided
+     */
+    if (IS_NULL(value)) {
+        JNU_ThrowNullPointerException(env, "value argument");
+        return;
+    }
+
+    /*
+     * Setting the multicast interface handled seperately
+     */
+    if (opt == java_net_SocketOptions_IP_MULTICAST_IF ||
+        opt == java_net_SocketOptions_IP_MULTICAST_IF2) {
+
+        setMulticastInterface(env, this, fd, opt, value);
+        return;
+    }
+
+    /*
+     * Setting the multicast loopback mode handled separately
+     */
+    if (opt == java_net_SocketOptions_IP_MULTICAST_LOOP) {
+        setMulticastLoopbackMode(env, this, fd, opt, value);
+        return;
+    }
+
+    /*
+     * Map the Java level socket option to the platform specific
+     * level and option name.
+     */
+    if (NET_MapSocketOption(opt, &level, &optname)) {
+        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 :
+            {
+                jclass cls;
+                jfieldID fid;
+
+                cls = (*env)->FindClass(env, "java/lang/Integer");
+                CHECK_NULL(cls);
+                fid =  (*env)->GetFieldID(env, cls, "value", "I");
+                CHECK_NULL(fid);
+
+                optval.i = (*env)->GetIntField(env, value, fid);
+                optlen = sizeof(optval.i);
+                break;
+            }
+
+        case java_net_SocketOptions_SO_REUSEADDR:
+        case java_net_SocketOptions_SO_BROADCAST:
+            {
+                jclass cls;
+                jfieldID fid;
+                jboolean on;
+
+                cls = (*env)->FindClass(env, "java/lang/Boolean");
+                CHECK_NULL(cls);
+                fid =  (*env)->GetFieldID(env, cls, "value", "Z");
+                CHECK_NULL(fid);
+
+                on = (*env)->GetBooleanField(env, value, fid);
+
+                /* SO_REUSEADDR or SO_BROADCAST */
+                optval.i = (on ? 1 : 0);
+                optlen = sizeof(optval.i);
+
+                break;
+            }
+
+        default :
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                "Socket option not supported by PlainDatagramSocketImp");
+            return;
+
+    }
+
+    if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option");
+        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
+ *              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.
+ */
+jobject getMulticastInterface(JNIEnv *env, jobject this, int fd, jint opt) {
+    if ((opt == java_net_SocketOptions_IP_MULTICAST_IF2) ||
+        (opt == java_net_SocketOptions_IP_MULTICAST_IF)) {
+        int index;
+        int len = sizeof(index);
+
+        if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                           (char*)&index, &len) < 0) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                           "Error getting socket option");
+            return NULL;
+        }
+
+        jobject ifindex = createInteger(env, index);
+        CHECK_NULL_RETURN(ifindex, NULL);
+        return ifindex;
+    }
+    return NULL;
+}
+
+
+
+/*
+ * Returns relevant info as a jint.
+ *
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    socketGetOption
+ * Signature: (I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL
+PlainDatagramSocketImpl_socketGetOption(JNIEnv *env, jobject this,
+                                                      jint opt) {
+    int fd;
+    int level, optname, optlen;
+    union {
+        int i;
+        char c;
+    } optval;
+
+    fd = getFD(env, this);
+    if (fd < 0) {
+        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, opt);
+
+    }
+
+    /*
+     * SO_BINDADDR implemented using getsockname
+     */
+    if (opt == java_net_SocketOptions_SO_BINDADDR) {
+        /* find out local IP address */
+        SOCKADDR him;
+        socklen_t len = 0;
+        int port;
+        jobject iaObj;
+
+        len = SOCKADDR_LEN;
+
+        if (getsockname(fd, (struct sockaddr *)&him, &len) == -1) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                           "Error getting socket name");
+            return NULL;
+        }
+        iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &port);
+
+        return iaObj;
+    }
+
+    /*
+     * Map the Java level socket option to the platform specific
+     * level and option name.
+     */
+    if (NET_MapSocketOption(opt, &level, &optname)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid option");
+        return NULL;
+    }
+
+    if (opt == java_net_SocketOptions_IP_MULTICAST_LOOP &&
+        level == IPPROTO_IP) {
+        optlen = sizeof(optval.c);
+    } else {
+        optlen = sizeof(optval.i);
+    }
+
+    if (NET_GetSockOpt(fd, level, optname, (void *)&optval, &optlen) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                         "Error getting socket option");
+        return NULL;
+    }
+
+    switch (opt) {
+        case java_net_SocketOptions_IP_MULTICAST_LOOP:
+            /* getLoopbackMode() returns true if IP_MULTICAST_LOOP disabled */
+            if (level == IPPROTO_IP) {
+                return createBoolean(env, (int)!optval.c);
+            } else {
+                return createBoolean(env, !optval.i);
+            }
+
+        case java_net_SocketOptions_SO_BROADCAST:
+        case java_net_SocketOptions_SO_REUSEADDR:
+            return createBoolean(env, optval.i);
+
+        case java_net_SocketOptions_SO_SNDBUF:
+        case java_net_SocketOptions_SO_RCVBUF:
+        case java_net_SocketOptions_IP_TOS:
+            return createInteger(env, optval.i);
+
+    }
+
+    /* should never reach here */
+    return NULL;
+}
+
+/*
+ * Multicast-related calls
+ */
+
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTTL(JNIEnv *env, jobject this,
+                                             jbyte ttl) {
+    jint ittl = ttl;
+    if (ittl < 0) {
+        ittl += 0x100;
+    }
+    PlainDatagramSocketImpl_setTimeToLive(env, this, ittl);
+}
+
+/*
+ * Set TTL for a socket. Throw exception if failed.
+ */
+static void setTTL(JNIEnv *env, int fd, jint ttl) {
+    char ittl = (char)ttl;
+    if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl,
+                       sizeof(ittl)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error setting socket option");
+    }
+}
+
+/*
+ * Set hops limit for a socket. Throw exception if failed.
+ */
+#ifdef AF_INET6
+static void setHopLimit(JNIEnv *env, int fd, jint ttl) {
+    int ittl = (int)ttl;
+    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+                       (char*)&ittl, sizeof(ittl)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error setting socket option");
+    }
+}
+#endif
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    setTTL
+ * Signature: (B)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTimeToLive(JNIEnv *env, jobject this,
+                                                    jint ttl) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int fd;
+    /* it is important to cast this to a char, otherwise setsockopt gets confused */
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    /* setsockopt to be correct TTL */
+#ifdef AF_INET6
+#ifdef __linux__
+    setTTL(env, fd, ttl);
+    if ((*env)->ExceptionCheck(env)) {
+        return;
+    }
+    if (ipv6_available()) {
+        setHopLimit(env, fd, ttl);
+    }
+#else  /*  __linux__ not defined */
+    if (ipv6_available()) {
+        setHopLimit(env, fd, ttl);
+    } else {
+        setTTL(env, fd, ttl);
+    }
+#endif  /* __linux__ */
+#else
+    setTTL(env, fd, ttl);
+#endif  /* AF_INET6 */
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jbyte JNICALL
+PlainDatagramSocketImpl_getTTL(JNIEnv *env, jobject this) {
+    return (jbyte)PlainDatagramSocketImpl_getTimeToLive(env, this);
+}
+
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_getTimeToLive(JNIEnv *env, jobject this) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint fd = -1;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return -1;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    /* getsockopt of TTL */
+#ifdef AF_INET6
+    if (ipv6_available()) {
+        int ttl = 0;
+        int len = sizeof(ttl);
+
+        if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+                               (char*)&ttl, &len) < 0) {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                               "Error getting socket option");
+                return -1;
+            }
+        return (jint)ttl;
+    } else
+#endif /* AF_INET6 */
+        {
+            u_char ttl = 0;
+            int len = sizeof(ttl);
+            if (JVM_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+                               (char*)&ttl, &len) < 0) {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                               "Error getting socket option");
+                return -1;
+            }
+            return (jint)ttl;
+        }
+}
+
+
+/*
+ * mcast_join_leave: Join or leave a multicast group.
+ *
+ * For IPv4 sockets use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group.
+ *
+ * For IPv6 sockets use IPV6_ADD_MEMBERSHIP/IPV6_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group. If multicast group is an IPv4 address then
+ * an IPv4-mapped address is used.
+ *
+ * On Linux with IPv6 if we wish to join/leave an IPv4 multicast group then
+ * we must use the IPv4 socket options. This is because the IPv6 socket options
+ * don't support IPv4-mapped addresses. This is true as per 2.2.19 and 2.4.7
+ * kernel releases. In the future it's possible that IP_ADD_MEMBERSHIP
+ * will be updated to return ENOPROTOOPT if uses with an IPv6 socket (Solaris
+ * already does this). Thus to cater for this we first try with the IPv4
+ * socket options and if they fail we use the IPv6 socket options. This
+ * seems a reasonable failsafe solution.
+ */
+static void mcast_join_leave(JNIEnv *env, jobject this,
+                             jobject iaObj, jobject niObj,
+                             jboolean join) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint fd;
+    jint ipv6_join_leave;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    if (IS_NULL(iaObj)) {
+        JNU_ThrowNullPointerException(env, "iaObj");
+        return;
+    }
+
+    /*
+     * Get java/net/NetworkInterface#index field.
+     */
+    static jfieldID ni_indexID;
+
+    if (ni_indexID == NULL) {
+      jclass c = (*env)->FindClass(env, "java/net/NetworkInterface");
+      CHECK_NULL(c);
+      ni_indexID = (*env)->GetFieldID(env, c, "index", "I");
+      CHECK_NULL(ni_indexID);
+    }
+
+    /*
+     * Determine if this is an IPv4 or IPv6 join/leave.
+     */
+    ipv6_join_leave = ipv6_available();
+
+    if (getInetAddress_family(env, iaObj) == IPv4) {
+        ipv6_join_leave = JNI_FALSE;
+    }
+
+    /*
+     * For IPv4 join use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+     *
+     * On Linux if IPv4 or IPv6 use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP
+     */
+    if (!ipv6_join_leave) {
+        struct ip_mreqn mname;
+        int mname_len;
+
+        /*
+         * joinGroup(InetAddress, NetworkInterface) implementation :-
+         *
+         * Linux/IPv6:  use ip_mreqn structure populated with multicast
+         *              address and interface index.
+         *
+         */
+        if (niObj != NULL) {
+            mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+            mname.imr_address.s_addr = 0;
+            mname.imr_ifindex =  (*env)->GetIntField(env, niObj, ni_indexID);
+            mname_len = sizeof(struct ip_mreqn);
+        }
+
+
+        /*
+         * joinGroup(InetAddress) implementation :-
+         *
+         * Linux/IPv6:  use ip_mreqn structure populated with multicast
+         *              address and interface index. index obtained
+         *              from cached value or IPV6_MULTICAST_IF.
+         *
+         * IPv4:        use ip_mreq structure populated with multicast
+         *              address and local address obtained from
+         *              IP_MULTICAST_IF. On Linux IP_MULTICAST_IF
+         *              returns different structure depending on
+         *              kernel.
+         */
+
+        if (niObj == NULL) {
+            int index;
+            int len = sizeof(index);
+
+            if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                               (char*)&index, &len) < 0) {
+                NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+                return;
+            }
+
+            mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+            mname.imr_address.s_addr = 0 ;
+            mname.imr_ifindex = index;
+            mname_len = sizeof(struct ip_mreqn);
+        }
+
+
+        /*
+         * Join the multicast group.
+         */
+        if (JVM_SetSockOpt(fd, IPPROTO_IP, (join ? IP_ADD_MEMBERSHIP:IP_DROP_MEMBERSHIP),
+                           (char *) &mname, mname_len) < 0) {
+
+            /*
+             * If IP_ADD_MEMBERSHIP returns ENOPROTOOPT on Linux and we've got
+             * IPv6 enabled then it's possible that the kernel has been fixed
+             * so we switch to IPV6_ADD_MEMBERSHIP socket option.
+             * As of 2.4.7 kernel IPV6_ADD_MEMBERSHIP can't handle IPv4-mapped
+             * addresses so we have to use IP_ADD_MEMBERSHIP for IPv4 multicast
+             * groups. However if the socket is an IPv6 socket then then setsockopt
+             * should return ENOPROTOOPT. We assume this will be fixed in Linux
+             * at some stage.
+             */
+            if (errno == ENOPROTOOPT) {
+                if (ipv6_available()) {
+                    ipv6_join_leave = JNI_TRUE;
+                    errno = 0;
+                } else  {
+                    errno = ENOPROTOOPT;    /* errno can be changed by ipv6_available */
+                }
+            }
+            if (errno) {
+                if (join) {
+                    NET_ThrowCurrent(env, "setsockopt IP_ADD_MEMBERSHIP failed");
+                } else {
+                    if (errno == ENOENT)
+                        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                            "Not a member of the multicast group");
+                    else
+                        NET_ThrowCurrent(env, "setsockopt IP_DROP_MEMBERSHIP failed");
+                }
+                return;
+            }
+        }
+
+        /*
+         * If we haven't switched to IPv6 socket option then we're done.
+         */
+        if (!ipv6_join_leave) {
+            return;
+        }
+    }
+
+
+    /*
+     * IPv6 join. If it's an IPv4 multicast group then we use an IPv4-mapped
+     * address.
+     */
+    {
+        struct ipv6_mreq mname6;
+
+        jbyteArray ipaddress;
+        jbyte caddr[16];
+        jint family;
+        jint address;
+        family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+        if (family == AF_INET) { /* will convert to IPv4-mapped address */
+            memset((char *) caddr, 0, 16);
+            address = getInetAddress_addr(env, iaObj);
+
+            caddr[10] = 0xff;
+            caddr[11] = 0xff;
+
+            caddr[12] = ((address >> 24) & 0xff);
+            caddr[13] = ((address >> 16) & 0xff);
+            caddr[14] = ((address >> 8) & 0xff);
+            caddr[15] = (address & 0xff);
+        } else {
+            // Android-changed: explicit cast to suppress compiler warning.
+            getInet6Address_ipaddress(env, iaObj, (char*)caddr);
+        }
+
+        memcpy((void *)&(mname6.ipv6mr_multiaddr), caddr, sizeof(struct in6_addr));
+        if (IS_NULL(niObj)) {
+            int index;
+            int len = sizeof(index);
+
+            if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                             (char*)&index, &len) < 0) {
+                NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+                return;
+            }
+
+            mname6.ipv6mr_interface = index;
+        } else {
+            jint idx = (*env)->GetIntField(env, niObj, ni_indexID);
+            mname6.ipv6mr_interface = idx;
+        }
+
+#define ADD_MEMBERSHIP          IPV6_ADD_MEMBERSHIP
+#define DRP_MEMBERSHIP          IPV6_DROP_MEMBERSHIP
+#define S_ADD_MEMBERSHIP        "IPV6_ADD_MEMBERSHIP"
+#define S_DRP_MEMBERSHIP        "IPV6_DROP_MEMBERSHIP"
+
+        /* Join the multicast group */
+        if (JVM_SetSockOpt(fd, IPPROTO_IPV6, (join ? ADD_MEMBERSHIP : DRP_MEMBERSHIP),
+                           (char *) &mname6, sizeof (mname6)) < 0) {
+
+            if (join) {
+                NET_ThrowCurrent(env, "setsockopt " S_ADD_MEMBERSHIP " failed");
+            } else {
+                if (errno == ENOENT) {
+                   JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Not a member of the multicast group");
+                } else {
+                    NET_ThrowCurrent(env, "setsockopt " S_DRP_MEMBERSHIP " failed");
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    join
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_join(JNIEnv *env, jobject this,
+                                           jobject iaObj, jobject niObj)
+{
+    mcast_join_leave(env, this, iaObj, niObj, JNI_TRUE);
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    leave
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_leave(JNIEnv *env, jobject this,
+                                            jobject iaObj, jobject niObj)
+{
+    mcast_join_leave(env, this, iaObj, niObj, JNI_FALSE);
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(PlainDatagramSocketImpl, leave, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, join, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, getTimeToLive, "()I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, getTTL, "()B"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, setTimeToLive, "(I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, setTTL, "(B)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, socketGetOption, "(I)Ljava/lang/Object;"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, socketSetOption0, "(ILjava/lang/Object;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketClose, "()V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketCreate, "()V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, receive0, "(Ljava/net/DatagramPacket;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, peekData, "(Ljava/net/DatagramPacket;)I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, peek, "(Ljava/net/InetAddress;)I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, send, "(Ljava/net/DatagramPacket;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, disconnect0, "(I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, connect0, "(Ljava/net/InetAddress;I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, bind0, "(ILjava/net/InetAddress;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, init, "()V"),
+};
+
+void register_java_net_PlainDatagramSocketImpl(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "java/net/PlainDatagramSocketImpl", gMethods, NELEM(gMethods));
+}
diff --git a/ojluni/src/main/native/Register.cpp b/ojluni/src/main/native/Register.cpp
index 2e586aa..99db9fa 100644
--- a/ojluni/src/main/native/Register.cpp
+++ b/ojluni/src/main/native/Register.cpp
@@ -127,6 +127,8 @@
     register_java_net_Inet4Address(env);
     register_java_net_Inet6Address(env);
     register_java_net_PlainSocketImpl(env);
+    register_java_net_PlainDatagramSocketImpl(env);
+    register_java_net_DatagramPacket(env);
     register_java_net_SocketInputStream(env);
     register_java_net_SocketOutputStream(env);
     register_java_nio_Bits(env);
diff --git a/ojluni/src/main/native/net_util.h b/ojluni/src/main/native/net_util.h
index 839e947..4b7310b 100644
--- a/ojluni/src/main/native/net_util.h
+++ b/ojluni/src/main/native/net_util.h
@@ -93,8 +93,6 @@
 extern jfieldID psi_portID;
 extern jfieldID psi_localportID;
 
-/* Android-removed: DatagramSocket moved away from JNI */
-#if 0
 /* DatagramPacket fields */
 extern jfieldID dp_addressID;
 extern jfieldID dp_portID;
@@ -102,7 +100,6 @@
 extern jfieldID dp_offsetID;
 extern jfieldID dp_lengthID;
 extern jfieldID dp_bufLengthID;
-#endif
 
 /* Inet6Address fields */
 extern jclass ia6_class;
diff --git a/ojluni/src/main/native/openjdksub.mk b/ojluni/src/main/native/openjdksub.mk
index a083a6b..da03db2 100644
--- a/ojluni/src/main/native/openjdksub.mk
+++ b/ojluni/src/main/native/openjdksub.mk
@@ -52,6 +52,8 @@
     Inet4Address.c \
     linux_close.cpp \
     PlainSocketImpl.c \
+    PlainDatagramSocketImpl.c \
+    DatagramPacket.c \
     ServerSocketChannelImpl.c \
     SocketInputStream.c \
     SocketOutputStream.c \