summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorNeil Fuller <nfuller@google.com>2014-01-30 14:17:54 +0000
committerNeil Fuller <nfuller@google.com>2014-02-28 16:02:08 +0000
commitdf29508a7aa622f265aaebdc472eb7d679185ebb (patch)
treee2ac108fcb0b350c8fe41033d26a4d272f926584 /luni
parentdc22c51199c848e9eaca7081165d6d9b94cf3389 (diff)
downloadlibcore-df29508a7aa622f265aaebdc472eb7d679185ebb.zip
libcore-df29508a7aa622f265aaebdc472eb7d679185ebb.tar.gz
libcore-df29508a7aa622f265aaebdc472eb7d679185ebb.tar.bz2
Implementation of MulticastChannel.
Note: ProtocolFamily / StandardProtocolFamily / DatagramChannel.open(ProtocolFamily) have not been implemented. There is a related change to libnativehelper that must be merged at the same time to avoid build breakage. See https://android-review.googlesource.com/#/c/81371/ Bug: 12464155 Change-Id: I07fc049b429a2d373e9bd7b07149632f5cd69f9d
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/java/net/MulticastSocket.java3
-rw-r--r--luni/src/main/java/java/nio/DatagramChannelImpl.java58
-rw-r--r--luni/src/main/java/java/nio/MembershipKeyImpl.java106
-rw-r--r--luni/src/main/java/java/nio/MulticastMembershipHandler.java496
-rw-r--r--luni/src/main/java/java/nio/ServerSocketChannelImpl.java11
-rw-r--r--luni/src/main/java/java/nio/SocketChannelImpl.java3
-rw-r--r--luni/src/main/java/java/nio/channels/DatagramChannel.java22
-rw-r--r--luni/src/main/java/java/nio/channels/MembershipKey.java121
-rw-r--r--luni/src/main/java/java/nio/channels/MulticastChannel.java160
-rw-r--r--luni/src/main/java/libcore/io/ForwardingOs.java1
-rw-r--r--luni/src/main/java/libcore/io/IoBridge.java58
-rw-r--r--luni/src/main/java/libcore/io/Os.java1
-rw-r--r--luni/src/main/java/libcore/io/OsConstants.java4
-rw-r--r--luni/src/main/java/libcore/io/Posix.java1
-rw-r--r--luni/src/main/java/libcore/io/StructGroupSourceReq.java43
-rw-r--r--luni/src/main/native/NetworkUtilities.h2
-rw-r--r--luni/src/main/native/libcore_io_OsConstants.cpp12
-rw-r--r--luni/src/main/native/libcore_io_Posix.cpp45
-rw-r--r--luni/src/test/java/libcore/java/nio/channels/DatagramChannelMulticastTest.java1073
-rw-r--r--luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java120
-rw-r--r--luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java15
-rw-r--r--luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java16
22 files changed, 2356 insertions, 15 deletions
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java
index 970468a..24e66c5 100644
--- a/luni/src/main/java/java/net/MulticastSocket.java
+++ b/luni/src/main/java/java/net/MulticastSocket.java
@@ -229,6 +229,9 @@ public class MulticastSocket extends DatagramSocket {
private void checkJoinOrLeave(InetAddress groupAddr) throws IOException {
checkOpen();
+ if (groupAddr == null) {
+ throw new IllegalArgumentException("groupAddress == null");
+ }
if (!groupAddr.isMulticastAddress()) {
throw new IOException("Not a multicast group: " + groupAddr);
}
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index cb940db..883ffea 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -27,6 +27,7 @@ import java.net.DatagramSocketImpl;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
import java.net.PlainDatagramSocketImpl;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -37,7 +38,9 @@ import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
+import java.nio.channels.MembershipKey;
import java.nio.channels.NotYetConnectedException;
+import java.nio.channels.UnresolvedAddressException;
import java.nio.channels.UnsupportedAddressTypeException;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
@@ -77,6 +80,9 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
private final Object readLock = new Object();
private final Object writeLock = new Object();
+ // A helper to manage multicast group membership. Created as required.
+ private MulticastMembershipHandler multicastMembershipHandler;
+
/*
* Constructor
*/
@@ -122,6 +128,9 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
}
InetSocketAddress localAddress = (InetSocketAddress) local;
+ if (localAddress.isUnresolved()) {
+ throw new UnresolvedAddressException();
+ }
IoBridge.bind(fd, localAddress.getAddress(), localAddress.getPort());
onBind(true /* updateSocketState */);
return this;
@@ -516,6 +525,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
// A closed channel is not connected.
onDisconnect(true /* updateSocketState */);
IoBridge.closeSocket(fd);
+ multicastMembershipHandler = null;
if (socket != null && !socket.isClosed()) {
socket.onClose();
@@ -529,7 +539,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
/*
* Status check, must be open.
*/
- private void checkOpen() throws IOException {
+ private void checkOpen() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
@@ -561,6 +571,52 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann
return fd;
}
+ @Override
+ synchronized public MembershipKey join(InetAddress groupAddress,
+ NetworkInterface networkInterface) throws IOException {
+
+ checkOpen();
+ ensureMembershipHandlerExists();
+ return multicastMembershipHandler.addAnySourceMembership(networkInterface, groupAddress);
+ }
+
+ @Override
+ synchronized public MembershipKey join(
+ InetAddress groupAddress, NetworkInterface networkInterface, InetAddress sourceAddress)
+ throws IOException {
+ checkOpen();
+ ensureMembershipHandlerExists();
+ return multicastMembershipHandler.addSourceSpecificMembership(
+ networkInterface, groupAddress, sourceAddress);
+ }
+
+ synchronized void multicastDrop(MembershipKeyImpl membershipKey) {
+ ensureMembershipHandlerExists();
+ multicastMembershipHandler.dropMembership(membershipKey);
+ }
+
+ synchronized void multicastBlock(MembershipKeyImpl membershipKey, InetAddress sourceAddress)
+ throws SocketException {
+
+ ensureMembershipHandlerExists();
+ multicastMembershipHandler.block(membershipKey, sourceAddress);
+ }
+
+ synchronized void multicastUnblock(MembershipKeyImpl membershipKey, InetAddress sourceAddress) {
+ ensureMembershipHandlerExists();
+ multicastMembershipHandler.unblock(membershipKey, sourceAddress);
+ }
+
+ /**
+ * Creates the {@code multicastMembershipHandler} if one doesn't already exist. Callers must
+ * handle synchronization.
+ */
+ private void ensureMembershipHandlerExists() {
+ if (multicastMembershipHandler == null) {
+ multicastMembershipHandler = new MulticastMembershipHandler(this);
+ }
+ }
+
/*
* The adapter class of DatagramSocket
*/
diff --git a/luni/src/main/java/java/nio/MembershipKeyImpl.java b/luni/src/main/java/java/nio/MembershipKeyImpl.java
new file mode 100644
index 0000000..3d7e957
--- /dev/null
+++ b/luni/src/main/java/java/nio/MembershipKeyImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.channels.MembershipKey;
+import java.nio.channels.MulticastChannel;
+
+/**
+ * An implementation of {@link MembershipKey}.
+ *
+ * To keep this class simple and keep all mutation operations in one place and easily synchronized,
+ * most socket logic is held in {@link java.nio.DatagramChannelImpl}.
+ */
+final class MembershipKeyImpl extends MembershipKey {
+
+ private final DatagramChannelImpl channel;
+ private final InetAddress groupAddress;
+ private final NetworkInterface networkInterface;
+ private final InetAddress sourceAddress;
+ private volatile boolean isValid;
+
+ public MembershipKeyImpl(DatagramChannelImpl channel, NetworkInterface networkInterface,
+ InetAddress groupAddress, InetAddress sourceAddress) {
+
+ this.channel = channel;
+ this.networkInterface = networkInterface;
+ this.groupAddress = groupAddress;
+ this.sourceAddress = sourceAddress;
+ this.isValid = true;
+ }
+
+ @Override
+ public boolean isValid() {
+ // invalidate() is called if the key is dropped, but for simplicity it is not
+ // invalidated when the channel is closed. Therefore, the channel must also be checked to see
+ // if it is still open.
+ return isValid && channel.isOpen();
+ }
+
+ void invalidate() {
+ this.isValid = false;
+ }
+
+ @Override
+ public void drop() {
+ channel.multicastDrop(this);
+ }
+
+ @Override
+ public MembershipKey block(InetAddress source) throws IOException {
+ channel.multicastBlock(this, source);
+ return this;
+ }
+
+ @Override
+ synchronized public MembershipKey unblock(InetAddress source) {
+ channel.multicastUnblock(this, source);
+ return this;
+ }
+
+ @Override
+ public MulticastChannel channel() {
+ return channel;
+ }
+
+ @Override
+ public InetAddress group() {
+ return groupAddress;
+ }
+
+ @Override
+ public NetworkInterface networkInterface() {
+ return networkInterface;
+ }
+
+ @Override
+ public InetAddress sourceAddress() {
+ return sourceAddress;
+ }
+
+ @Override
+ public String toString() {
+ return "MembershipKeyImpl{" +
+ "groupAddress=" + groupAddress +
+ ", networkInterface=" + networkInterface +
+ ", sourceAddress=" + sourceAddress +
+ '}';
+ }
+}
diff --git a/luni/src/main/java/java/nio/MulticastMembershipHandler.java b/luni/src/main/java/java/nio/MulticastMembershipHandler.java
new file mode 100644
index 0000000..7564d4e
--- /dev/null
+++ b/luni/src/main/java/java/nio/MulticastMembershipHandler.java
@@ -0,0 +1,496 @@
+package java.nio;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.channels.MembershipKey;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoBridge;
+import libcore.io.StructGroupReq;
+import libcore.io.StructGroupSourceReq;
+
+/**
+ * A helper class for {@link DatagramChannelImpl} that keeps track of multicast group
+ * memberships. This class is not threadsafe, and relies on the DatagramChannelImpl to synchronize.
+ *
+ * <p>See <a href="http://tools.ietf.org/html/rfc3678">RFC 3678</a> for context and terminology.
+ */
+final class MulticastMembershipHandler {
+
+ private final DatagramChannelImpl channel;
+ private final Map<Id, Membership> memberships = new HashMap<Id, Membership>();
+
+ MulticastMembershipHandler(DatagramChannelImpl channel) {
+ this.channel = channel;
+ }
+
+ /**
+ * The implementation for
+ * {@link java.nio.channels.MulticastChannel#join(InetAddress, NetworkInterface)}.
+ */
+ public MembershipKeyImpl addAnySourceMembership(
+ NetworkInterface networkInterface, InetAddress groupAddress) throws SocketException {
+
+ validateMulticastGroupArgs(groupAddress, networkInterface);
+ assertChannelOpen();
+
+ Id id = new Id(networkInterface, groupAddress);
+ Membership membership = memberships.get(id);
+ if (membership != null) {
+ return membership.getAnySourceMembershipKey();
+ }
+
+ // No existing membership found. Attempt to join.
+ StructGroupReq groupReq = makeGroupReq(groupAddress, networkInterface);
+ IoBridge.setSocketOption(channel.getFD(), IoBridge.JAVA_MCAST_JOIN_GROUP, groupReq);
+
+ // Record the membership and return the key.
+ membership = Membership.createAnySource(channel, networkInterface, groupAddress);
+ memberships.put(id, membership);
+ return membership.getAnySourceMembershipKey();
+ }
+
+ /**
+ * The implementation for
+ * {@link java.nio.channels.MulticastChannel#join(InetAddress, NetworkInterface, InetAddress)}.
+ */
+ public MembershipKeyImpl addSourceSpecificMembership(
+ NetworkInterface networkInterface, InetAddress groupAddress, InetAddress sourceAddress)
+ throws SocketException {
+
+ validateMulticastGroupArgs(groupAddress, networkInterface);
+ validateSourceAddress(sourceAddress);
+ validateAddressProtocolTheSame(groupAddress, sourceAddress);
+ assertChannelOpen();
+
+ Id id = new Id(networkInterface, groupAddress);
+ Membership membership = memberships.get(id);
+ if (membership != null) {
+ MembershipKeyImpl existingMembershipKey =
+ membership.getSourceSpecificMembershipKey(sourceAddress);
+ if (existingMembershipKey != null) {
+ return existingMembershipKey;
+ }
+ }
+
+ // No existing membership found. Attempt to join.
+ IoBridge.setSocketOption(channel.getFD(), IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP,
+ makeGroupSourceReq(groupAddress, networkInterface, sourceAddress));
+
+ if (membership == null) {
+ // Record the membership and return the key.
+ membership = Membership.createSourceSpecific(
+ channel, networkInterface, groupAddress, sourceAddress);
+ memberships.put(id, membership);
+ return membership.getSourceSpecificMembershipKey(sourceAddress);
+ } else {
+ // Add a new source to the existing membership.
+ return membership.addSource(sourceAddress);
+ }
+ }
+
+ /**
+ * The implementation for {@link MembershipKey#drop()}.
+ */
+ public void dropMembership(MembershipKeyImpl membershipKey) {
+ // For compatibility with the RI, this is one case where the membershipKey can no longer be
+ // valid.
+ if (!membershipKey.isValid()) {
+ return;
+ }
+ if (membershipKey.channel() != this.channel) {
+ throw new AssertionError("Bad membership key");
+ }
+ assertChannelOpen();
+
+ Id id = createId(membershipKey);
+ Membership membership = memberships.get(id);
+ if (membership == null) {
+ throw new AssertionError("Bad membership key" + membershipKey);
+ }
+
+ if (!membership.isSourceSpecific()) {
+ try {
+ StructGroupReq groupReq =
+ makeGroupReq(membershipKey.group(), membershipKey.networkInterface());
+ IoBridge.setSocketOption(channel.getFD(), IoBridge.JAVA_MCAST_LEAVE_GROUP, groupReq);
+ } catch (SocketException e) {
+ // TODO: Obtain opinion on how to report this, throw this or if it is safe to ignore.
+ throw new IllegalStateException(e);
+ }
+ memberships.remove(id);
+ } else {
+ StructGroupSourceReq groupSourceReq = makeGroupSourceReq(membershipKey.group(),
+ membershipKey.networkInterface(), membershipKey.sourceAddress());
+
+ try {
+ IoBridge.setSocketOption(
+ channel.getFD(), IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP,
+ groupSourceReq);
+ } catch (SocketException e) {
+ // TODO: Obtain opinion on how to report this, throw this or if it is safe to ignore.
+ throw new IllegalStateException(e);
+ }
+
+ boolean isLast = membership.removeSource(membershipKey.sourceAddress());
+ if (isLast) {
+ memberships.remove(id);
+ }
+ }
+ membershipKey.invalidate();
+ }
+
+ /**
+ * The implementation for {@link MembershipKey#block(java.net.InetAddress)}.
+ */
+ public void block(MembershipKeyImpl membershipKey, InetAddress sourceAddress)
+ throws SocketException {
+ validateMembershipKey(membershipKey);
+ validateSourceAddress(sourceAddress);
+ validateAddressProtocolTheSame(membershipKey.group(), sourceAddress);
+ assertChannelOpen();
+
+ Membership membership = getMembershipForKey(membershipKey);
+ if (membership == null) {
+ throw new AssertionError("Bad membership key" + membershipKey);
+ }
+
+ if (membership.isBlocked(sourceAddress)) {
+ return;
+ }
+
+ IoBridge.setSocketOption(channel.getFD(), IoBridge.JAVA_MCAST_BLOCK_SOURCE,
+ makeGroupSourceReq(
+ membershipKey.group(), membershipKey.networkInterface(), sourceAddress));
+
+ membership.block(sourceAddress);
+ }
+
+ /**
+ * The implementation for {@link MembershipKey#unblock(java.net.InetAddress)}.
+ */
+ public void unblock(MembershipKeyImpl membershipKey, InetAddress sourceAddress) {
+ validateMembershipKey(membershipKey);
+ validateSourceAddress(sourceAddress);
+ validateAddressProtocolTheSame(membershipKey.group(), sourceAddress);
+ assertChannelOpen();
+
+ Membership membership = getMembershipForKey(membershipKey);
+ if (membership == null) {
+ throw new AssertionError("Bad membership key" + membershipKey);
+ }
+
+ if (!membership.isBlocked(sourceAddress)) {
+ throw new IllegalStateException(
+ "sourceAddress " + sourceAddress + " is not blocked for " + membership.debugId());
+ }
+
+ try {
+ IoBridge.setSocketOption(channel.getFD(), IoBridge.JAVA_MCAST_UNBLOCK_SOURCE,
+ makeGroupSourceReq(membershipKey.group(), membershipKey.networkInterface(),
+ sourceAddress));
+ } catch (SocketException e) {
+ throw new IllegalStateException(e);
+ }
+
+ membership.unblock(sourceAddress);
+ }
+
+ private Membership getMembershipForKey(MembershipKey membershipKey) {
+ Id id = createId(membershipKey);
+ Membership membership = memberships.get(id);
+ if (membership == null) {
+ throw new AssertionError("No membership found for id " + id);
+ }
+ return membership;
+ }
+
+ private void assertChannelOpen() {
+ if (!channel.isOpen()) {
+ throw new AssertionError("Channel is closed");
+ }
+ }
+
+ private void validateMembershipKey(MembershipKeyImpl membershipKey) {
+ if (membershipKey.channel() != this.channel) {
+ throw new AssertionError("Invalid or bad membership key");
+ }
+ if (!membershipKey.isValid()) {
+ throw new IllegalStateException("Membership key is no longer valid: " + membershipKey);
+ }
+ }
+
+ private static Id createId(MembershipKey membershipKey) {
+ return new Id(membershipKey.networkInterface(), membershipKey.group());
+ }
+
+ private static void validateSourceAddress(InetAddress sourceAddress) {
+ if (sourceAddress.isAnyLocalAddress()) {
+ throw new IllegalArgumentException(
+ "sourceAddress must not be a wildcard address, is " + sourceAddress);
+ }
+ if (sourceAddress.isMulticastAddress()) {
+ throw new IllegalArgumentException(
+ "sourceAddress must be a unicast address, is " + sourceAddress);
+ }
+ }
+
+ private static void validateMulticastGroupArgs(
+ InetAddress groupAddress, NetworkInterface networkInterface) throws SocketException {
+
+ if (groupAddress == null) {
+ // RI throws NullPointerException.
+ throw new NullPointerException("groupAddress == null");
+ }
+ if (networkInterface == null) {
+ // RI throws NullPointerException.
+ throw new NullPointerException("networkInterface == null");
+ }
+ if (!networkInterface.isLoopback() && !networkInterface.supportsMulticast()) {
+ throw new IllegalArgumentException(
+ "networkInterface " + networkInterface + " does not support multicast");
+ }
+ if (!groupAddress.isMulticastAddress()) {
+ throw new IllegalArgumentException("Not a multicast group: " + groupAddress);
+ }
+ }
+
+ private static void validateAddressProtocolTheSame(
+ InetAddress groupAddress, InetAddress sourceAddress) {
+
+ if (groupAddress.getClass() != sourceAddress.getClass()) {
+ throw new IllegalArgumentException("Mixed address types not permitted: groupAddress: " +
+ groupAddress + ", sourceAddress: " + sourceAddress);
+ }
+ }
+
+ private static StructGroupSourceReq makeGroupSourceReq(
+ InetAddress gsr_group, NetworkInterface networkInterface, InetAddress gsr_source) {
+ int gsr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
+ return new StructGroupSourceReq(gsr_interface, gsr_group, gsr_source);
+ }
+
+ private static StructGroupReq makeGroupReq(InetAddress gr_group,
+ NetworkInterface networkInterface) {
+ int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
+ return new StructGroupReq(gr_interface, gr_group);
+ }
+
+ /**
+ * Membership information associated with an {@link Id}. A membership can be one of two types:
+ * "source-specific" and "any-source". The two types a mutually exclusive for a given Id.
+ */
+ static final class Membership {
+
+ private final DatagramChannelImpl channel;
+ private final InetAddress groupAddress;
+ private final NetworkInterface networkInterface;
+
+ // Any-source membership key. Mutually exclusive with sourceSpecificMembershipKeys.
+ private final MembershipKeyImpl anySourceMembershipKey;
+ // Blocked source addresses for any-source memberships. Assigned when required.
+ private Set<InetAddress> blockedSourceAddresses;
+
+ // Source-specific membership keys. Mutually exclusive with anySourceMembershipKey.
+ private final Map<InetAddress, MembershipKeyImpl> sourceSpecificMembershipKeys;
+
+ /** Use {@link #createSourceSpecific} or {@link #createAnySource} to construct. */
+ private Membership(
+ DatagramChannelImpl channel,
+ InetAddress groupAddress,
+ NetworkInterface networkInterface,
+ MembershipKeyImpl anySourceMembershipKey,
+ Map<InetAddress, MembershipKeyImpl> sourceSpecificMembershipKeys) {
+
+ this.channel = channel;
+ this.groupAddress = groupAddress;
+ this.networkInterface = networkInterface;
+ this.anySourceMembershipKey = anySourceMembershipKey;
+ this.sourceSpecificMembershipKeys = sourceSpecificMembershipKeys;
+ }
+
+ /** Creates an any-source membership. */
+ public static Membership createAnySource(DatagramChannelImpl channel,
+ NetworkInterface networkInterface, InetAddress groupAddress) {
+
+ MembershipKeyImpl withoutSourceAddressKey =
+ new MembershipKeyImpl(channel, networkInterface, groupAddress, null /* sourceAddress */);
+ return new Membership(
+ channel, groupAddress, networkInterface, withoutSourceAddressKey,
+ null /* sourceSpecificMembershipKeys */);
+ }
+
+ /**
+ * Creates a source-specific membership. See {@link #addSource} to add additional source
+ * addresses.
+ */
+ public static Membership createSourceSpecific(DatagramChannelImpl channel,
+ NetworkInterface networkInterface, InetAddress groupAddress, InetAddress sourceAddress) {
+
+ Map<InetAddress, MembershipKeyImpl> withSourceKeys =
+ new HashMap<InetAddress, MembershipKeyImpl>();
+ Membership membership = new Membership(
+ channel, groupAddress, networkInterface, null /* anySourceMembershipKey */,
+ withSourceKeys);
+ membership.addSource(sourceAddress);
+ return membership;
+ }
+
+ /**
+ * Adds a new source address filter to an existing membership, returning the associated
+ * {@link MembershipKeyImpl}. Throws an {@code IllegalStateException} if this is an
+ * any-source membership.
+ */
+ public MembershipKeyImpl addSource(InetAddress sourceAddress) {
+ if (sourceSpecificMembershipKeys == null) {
+ throw new IllegalStateException(
+ "Can only add sources to source-specific memberships: " + debugId());
+ }
+
+ MembershipKeyImpl membershipKey =
+ new MembershipKeyImpl(channel, networkInterface, groupAddress, sourceAddress);
+ sourceSpecificMembershipKeys.put(sourceAddress, membershipKey);
+ return membershipKey;
+ }
+
+ /**
+ * Removes the specified {@code sourceAddress} from the set of filters. Returns {@code true} if
+ * the set of filters is now empty. Throws an {@code IllegalStateException} if this is an
+ * any-source membership.
+ */
+ public boolean removeSource(InetAddress sourceAddress) {
+ if (sourceSpecificMembershipKeys == null) {
+ throw new IllegalStateException(
+ "Can only remove sources from source-specific memberships: " + debugId());
+ }
+ sourceSpecificMembershipKeys.remove(sourceAddress);
+ return sourceSpecificMembershipKeys.isEmpty();
+ }
+
+ /**
+ * Returns {@code true} if the membership source-specific, false if it is any-source.
+ */
+ public boolean isSourceSpecific() {
+ return sourceSpecificMembershipKeys != null;
+ }
+
+ /**
+ * Returns the {@link MembershipKeyImpl} for this membership. Throws an
+ * {@code IllegalStateException} if this is not an any-source membership.
+ */
+ public MembershipKeyImpl getAnySourceMembershipKey() {
+ if (sourceSpecificMembershipKeys != null) {
+ throw new IllegalStateException(
+ "There an existing source-specific membership for " + debugId());
+ }
+ return anySourceMembershipKey;
+ }
+
+ /**
+ * Returns the {@link MembershipKeyImpl} for the specified {@code sourceAddress}. Throws an
+ * {@code IllegalStateException} if this is not a source-specific membership.
+ */
+ public MembershipKeyImpl getSourceSpecificMembershipKey(InetAddress sourceAddress) {
+ if (anySourceMembershipKey != null) {
+ throw new IllegalStateException("There an existing any-source membership for " + debugId());
+ }
+ return sourceSpecificMembershipKeys.get(sourceAddress);
+ }
+
+ /**
+ * Returns {@code true} if there is an existing block for the specified address. Throws an
+ * {@code IllegalStateException} if this is not an any-source membership.
+ */
+ public boolean isBlocked(InetAddress sourceAddress) {
+ if (anySourceMembershipKey == null) {
+ throw new IllegalStateException(
+ "block()/unblock() are only applicable for any-source memberships: " + debugId());
+ }
+ return blockedSourceAddresses != null && blockedSourceAddresses.contains(sourceAddress);
+ }
+
+ /**
+ * Adds a blocked address to this membership. Throws an {@code IllegalStateException} if
+ * the address is already blocked. Throws an {@code IllegalStateException} if this is not an
+ * any-source membership.
+ */
+ public void block(InetAddress sourceAddress) {
+ if (anySourceMembershipKey == null) {
+ throw new IllegalStateException(
+ "block() is not supported for source-specific group memberships: " + debugId());
+ }
+ if (blockedSourceAddresses == null) {
+ blockedSourceAddresses = new HashSet<InetAddress>();
+ }
+ if (!blockedSourceAddresses.add(sourceAddress)) {
+ throw new IllegalStateException(
+ "Could not block " + sourceAddress + ": it was already blocked for " + debugId());
+ }
+ }
+
+ /**
+ * Removes a blocked address from this membership. Throws an {@code IllegalStateException} if
+ * the address is not blocked. Throws an {@code IllegalStateException} if this is not an
+ * any-source membership.
+ */
+ public void unblock(InetAddress sourceAddress) {
+ if (anySourceMembershipKey == null) {
+ throw new IllegalStateException(
+ "unblock() is not supported for source-specific group memberships: " + debugId());
+ }
+ if (blockedSourceAddresses == null || !blockedSourceAddresses.remove(sourceAddress)) {
+ throw new IllegalStateException(
+ "Could not unblock " + sourceAddress + ": it was not blocked for " + debugId());
+ }
+ }
+
+ public String debugId() {
+ return "<" + networkInterface + ":" + groupAddress + ">";
+ }
+
+ }
+
+ /** An identifier for a multicast group membership, independent of membership type. */
+ private static final class Id {
+
+ private final InetAddress groupAddress;
+ private final NetworkInterface networkInterface;
+
+ public Id(NetworkInterface networkInterface, InetAddress groupAddress) {
+ this.groupAddress = groupAddress;
+ this.networkInterface = networkInterface;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Id)) {
+ return false;
+ }
+
+ Id id = (Id) o;
+
+ if (!groupAddress.equals(id.groupAddress)) {
+ return false;
+ }
+ if (!networkInterface.equals(id.networkInterface)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = groupAddress.hashCode();
+ result = 31 * result + networkInterface.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
index 3d6cd22..5adea1a 100644
--- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
@@ -32,6 +32,7 @@ import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
+import java.nio.channels.UnresolvedAddressException;
import java.nio.channels.UnsupportedAddressTypeException;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
@@ -67,8 +68,14 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD
if (socket.isBound()) {
throw new AlreadyBoundException();
}
- if (localAddr != null && !(localAddr instanceof InetSocketAddress)) {
- throw new UnsupportedAddressTypeException();
+ if (localAddr != null) {
+ if (!(localAddr instanceof InetSocketAddress)) {
+ throw new UnsupportedAddressTypeException();
+ }
+ InetSocketAddress localInetAddress = (InetSocketAddress) localAddr;
+ if (localInetAddress.isUnresolved()) {
+ throw new UnresolvedAddressException();
+ }
}
socket.bind(localAddr, backlog);
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index 5d3db43..d1e77d6 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -157,6 +157,9 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
}
InetSocketAddress localAddress = (InetSocketAddress) local;
+ if (localAddress.isUnresolved()) {
+ throw new UnresolvedAddressException();
+ }
IoBridge.bind(fd, localAddress.getAddress(), localAddress.getPort());
onBind(true /* updateSocketState */);
return this;
diff --git a/luni/src/main/java/java/nio/channels/DatagramChannel.java b/luni/src/main/java/java/nio/channels/DatagramChannel.java
index 2aa228d..3a5d1cc 100644
--- a/luni/src/main/java/java/nio/channels/DatagramChannel.java
+++ b/luni/src/main/java/java/nio/channels/DatagramChannel.java
@@ -19,6 +19,8 @@ package java.nio.channels;
import java.io.IOException;
import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.ByteBuffer;
@@ -42,7 +44,7 @@ import java.util.Set;
* same time.
*/
public abstract class DatagramChannel extends AbstractSelectableChannel
- implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {
+ implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, MulticastChannel {
/**
* Constructs a new {@code DatagramChannel}.
@@ -130,6 +132,24 @@ public abstract class DatagramChannel extends AbstractSelectableChannel
throw new UnsupportedOperationException("Subclasses must override this method");
}
+ /** @hide Until ready for a public API change */
+ @Override
+ public MembershipKey join(InetAddress groupAddress, NetworkInterface networkInterface)
+ throws IOException {
+ // This method was added for interoperability with Java 7, where it is abstract. It is
+ // concrete here to avoid breaking existing Android applications that extend this class.
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /** @hide Until ready for a public API change */
+ @Override
+ public MembershipKey join(InetAddress groupAddress, NetworkInterface networkInterface,
+ InetAddress sourceAddress) throws IOException {
+ // This method was added for interoperability with Java 7, where it is abstract. It is
+ // concrete here to avoid breaking existing Android applications that extend this class.
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
/**
* Returns whether this channel's socket is connected or not.
*
diff --git a/luni/src/main/java/java/nio/channels/MembershipKey.java b/luni/src/main/java/java/nio/channels/MembershipKey.java
new file mode 100644
index 0000000..18ff92d
--- /dev/null
+++ b/luni/src/main/java/java/nio/channels/MembershipKey.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+
+/**
+ * A token produced as the result of joining a multicast group with
+ * {@link DatagramChannel#join(java.net.InetAddress, java.net.NetworkInterface)} or
+ * {@link DatagramChannel#join(java.net.InetAddress, java.net.NetworkInterface,
+ * java.net.InetAddress)}.
+ *
+ * <p>A multicast group membership can be source-specific or any-source. Source-specific memberships
+ * only allow datagrams from a single source address to be received. Any-source memberships
+ * initially allow datagrams from any source address to be received, but may have individual unicast
+ * IP addresses blocked via {@link #block(java.net.InetAddress)}. Any-source membership keys return
+ * {@code null} from {@link #sourceAddress()}.
+ *
+ * <p>See <a href="http://tools.ietf.org/html/rfc3678">RFC 3678: Socket Interface Extensions for
+ * Multicast Source Filters</a> for concepts and terminology associated with multicast membership.
+ *
+ * @since 1.7
+ * @hide Until ready for a public API change
+ */
+public abstract class MembershipKey {
+
+ protected MembershipKey() {}
+
+ /**
+ * Returns {@code true} until the membership is dropped with {@link #drop()} or the associated
+ * channel is closed.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Drops this membership from the multicast group, invalidating this key.
+ */
+ public abstract void drop();
+
+ /**
+ * Blocks datagrams from the specified source address; after this call any datagrams received from
+ * the address will be discarded. Blocking an already-blocked source address has no effect. A
+ * blocked address can be unblocked by calling {@link #unblock(java.net.InetAddress)}.
+ *
+ * <p>The block may not take effect instantaneously: datagrams that are already buffered by the
+ * underlying OS may still be delivered.
+ *
+ * <p>There is an OS-level limit on the number of source addresses that can be block for a given
+ * {@code groupAddress}, {@code networkInterface} pair. This is typically 10. Attempts to add
+ * more than this result in a {@code SocketException}.
+ *
+ * <p>If this membership key is source-specific an {@link IllegalStateException} is thrown.
+ *
+ * @throws IllegalStateException
+ * if this membership key is no longer valid or is of the wrong type
+ * @throws IllegalArgumentException
+ * if the source address is not unicast address of the same type as the multicast group
+ * address supplied when the group was joined
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ public abstract MembershipKey block(InetAddress source) throws IOException;
+
+ /**
+ * Unblocks datagrams from the specified source address that were previously blocked with a call
+ * to {@link #block(java.net.InetAddress)}; after this call any datagrams received from the
+ * address will be received. Unblocking an address that is not currently blocked throws an
+ * {@code IllegalStateException}.
+ *
+ * <p>If this membership key is source-specific an {@link IllegalStateException} is thrown.
+ *
+ * @throws IllegalStateException
+ * if this membership key is no longer valid or is of the wrong type, or the address is
+ * not currently blocked
+ * @throws IllegalArgumentException
+ * if the source address is not unicast address of the same type as the multicast group
+ * address supplied when the group was joined
+ */
+ public abstract MembershipKey unblock(InetAddress source);
+
+ /**
+ * Returns the {@code MulticastChannel} associated with this key. Continues returning the value
+ * even when the key has been invalidated.
+ */
+ public abstract MulticastChannel channel();
+
+ /**
+ * Returns the multicast group address associated with this key. Continues returning the value
+ * even when the key has been invalidated.
+ */
+ public abstract InetAddress group();
+
+ /**
+ * Returns the network interface associated with this key. Continues returning the value
+ * even when the key has been invalidated.
+ */
+ public abstract NetworkInterface networkInterface();
+
+ /**
+ * Returns the source address associated with this key if the membership is source-specific.
+ * Returns {@code null} if the membership is any-source. Continues returning the value
+ * even when the key has been invalidated.
+ */
+ public abstract InetAddress sourceAddress();
+}
diff --git a/luni/src/main/java/java/nio/channels/MulticastChannel.java b/luni/src/main/java/java/nio/channels/MulticastChannel.java
new file mode 100644
index 0000000..41ef501
--- /dev/null
+++ b/luni/src/main/java/java/nio/channels/MulticastChannel.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+
+/**
+ * A type of {@link NetworkChannel} that supports IP multicasting. IP multicasting allows for
+ * efficient routing of an IP datagram to multiple hosts. Hosts wishing to receive multicast
+ * datagrams join a multicast group identified by a multicast IP address.
+ *
+ * <p>Any datagram socket can be used to <em>send</em> to a multicast group: senders <em>do not</em>
+ * have to be a member of the group.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc2236.txt">RFC 2236: Internet Group Management
+ * Protocol, Version 2</a> and <a href="http://www.ietf.org/rfc/rfc3376.txt">RFC 3376: Internet
+ * Group Management Protocol, Version 3</a> for network-level information regarding IPv4 group
+ * membership. See <a href="http://www.ietf.org/rfc/rfc2710.txt">RFC 2710: Multicast Listener
+ * Discovery (MLD) for IPv6</a> and <a href="http://www.ietf.org/rfc/rfc3810.txt">RFC 3810:
+ * Multicast Listener Discovery Version 2 (MLDv2) for IPv6</a> for equivalent IPv6 information.
+ *
+ * <p>See <a href="http://tools.ietf.org/html/rfc3678">RFC 3678: Socket Interface Extensions for
+ * Multicast Source Filters</a> for concepts and terminology associated with multicast membership.
+ *
+ * <p>IP multicast requires support from network infrastructure; networks may not support
+ * all features of IP multicast.
+ *
+ * <p>A channel can be restricted to send multicast datagrams through a specific
+ * {@link NetworkInterface} by using {@link #setOption(java.net.SocketOption, Object)} with
+ * {@link java.net.StandardSocketOptions#IP_MULTICAST_IF}.
+ *
+ * <p>A channel may or may not receive multicast datagrams sent from this host, determined by the
+ * {@link java.net.StandardSocketOptions#IP_MULTICAST_LOOP} option.
+ *
+ * <p>The time-to-live for multicast datagrams can be set using the
+ * {@link java.net.StandardSocketOptions#IP_MULTICAST_TTL} option.
+ *
+ * <p>Usually multicast channels should have {@link java.net.StandardSocketOptions#SO_REUSEADDR}
+ * set to {@code true} before binding to enable multiple sockets on this host to be members of
+ * the same multicast group.
+ *
+ * <p>Typically multicast channels are {@link NetworkChannel#bind bound} to a wildcard address
+ * such as "0.0.0.0" (IPv4) or "::" (IPv6). They may also be bound to a multicast group address.
+ * Binding to a multicast group address restricts datagrams received to only those sent to the
+ * multicast group. When the wildcard address is used the socket may join multiple groups and also
+ * receive non-multicast datagrams sent directly to the host. The port the channel is bound to is
+ * important: only datagrams sent to the group on that port will be received.
+ *
+ * <p>Having bound a channel, the group can be joined. Memberships are either "any-source" or
+ * "source-specific". The type of membership is determined by the variant of {@code join} that is
+ * used. See {@link #join(java.net.InetAddress, java.net.NetworkInterface)} and
+ * {@link #join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress)} for more
+ * information.
+ *
+ * @since 1.7
+ * @hide Until ready for a public API change
+ */
+public interface MulticastChannel extends NetworkChannel {
+
+ // @hide Until ready for a public API change
+ // /**
+ // * {@inheritDoc}
+ // *
+ // * If the channel is currently part of one or more multicast groups then the memberships are
+ // * dropped and any associated {@code MembershipKey} objects are invalidated.
+ // */
+ void close() throws IOException;
+
+ /**
+ * Creates an any-source membership to the {@code groupAddress} on the specified
+ * {@code networkInterface}. Returns a {@code MembershipKey} that can be used to modify or
+ * {@link MembershipKey#drop()} the membership. See {@link MembershipKey#block(InetAddress)} and
+ * {@link MembershipKey#unblock(InetAddress)} for methods to modify source-address
+ * filtering.
+ *
+ * <p>A channel may join several groups. Each membership is network interface-specific: an
+ * application must join the group for each applicable network interface to receive datagrams.
+ *
+ * <p>Any-source and source-specific memberships cannot be mixed for a group address on a given
+ * network interface. An {@code IllegalStateException} will be thrown if joins of different types
+ * are attempted for a given {@code groupAddress}, {@code networkInterface} pair. Joining a
+ * multicast group with the same arguments as an existing, valid membership returns the same
+ * {@code MembershipKey}.
+ *
+ * <p>There is an OS-level limit on the number of multicast groups a process can join.
+ * This is typically 20. Attempts to join more than this result in a {@code SocketException}.
+ *
+ * @param groupAddress the multicast group address to join
+ * @param networkInterface the network address to join with
+ * @throws IllegalArgumentException
+ * if the group address is not a multicast address or the network interface does not
+ * support multicast
+ * @throws IllegalStateException
+ * if the channel already has a source-specific membership for the group/network interface
+ * @throws ClosedChannelException
+ * if the channel is closed
+ * @throws IOException
+ * if some other I/O error occurs
+ * @hide Until ready for a public API change
+ */
+ MembershipKey join(InetAddress groupAddress, NetworkInterface networkInterface)
+ throws IOException;
+
+ /**
+ * Creates a source-specific membership to the {@code groupAddress} on the specified
+ * {@code networkInterface} filtered by the {@code sourceAddress}. Returns a
+ * {@code MembershipKey} that can be used to {@link MembershipKey#drop()} the membership.
+ *
+ * <p>A channel may join several groups. Each membership is network interface-specific: an
+ * application must join the group for each applicable network interface to receive datagrams.
+ *
+ * <p>Any-source and source-specific memberships cannot be mixed for a group address on a given
+ * network interface. An {@code IllegalStateException} will be thrown if joins of different types
+ * are attempted for a given {@code groupAddress}, {@code networkInterface} pair. Joining a
+ * multicast group with the same arguments as an existing, valid membership returns the same
+ * {@code MembershipKey}.
+ *
+ * <p>There is an OS-level limit on the number of multicast groups a process can join.
+ * This is typically 20. Attempts to join more than this result in a {@code SocketException}.
+ *
+ * <p>There is an OS-level limit on the number of source addresses that can be joined for a given
+ * {@code groupAddress}, {@code networkInterface} pair. This is typically 10. Attempts to add
+ * more than this result in a {@code SocketException}.
+ *
+ * @param groupAddress the multicast group address to join
+ * @param networkInterface the network address to join with
+ * @param sourceAddress the source address to restrict datagrams to
+ * @throws IllegalArgumentException
+ * if the group address is not a multicast address, the network interface does not
+ * support multicast, or the {@code groupAddress} and {@code sourceAddress} are not of
+ * compatible types
+ * @throws IllegalStateException
+ * if the channel already has a source-specific membership for the group/network interface
+ * @throws ClosedChannelException
+ * if the channel is closed
+ * @throws IOException
+ * if some other I/O error occurs
+ * @hide Until ready for a public API change
+ */
+ MembershipKey join(
+ InetAddress groupAddress, NetworkInterface networkInterface, InetAddress sourceAddress)
+ throws IOException;
+
+}
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 64734a0..2de35e9 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -124,6 +124,7 @@ public class ForwardingOs implements Os {
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptInt(fd, level, option, value); }
public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptIpMreqn(fd, level, option, value); }
public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { os.setsockoptGroupReq(fd, level, option, value); }
+ public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { os.setsockoptGroupSourceReq(fd, level, option, value); }
public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { os.setsockoptLinger(fd, level, option, value); }
public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { os.setsockoptTimeval(fd, level, option, value); }
public void setuid(int uid) throws ErrnoException { os.setuid(uid); }
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index a48a0ad..08d4837 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -71,16 +71,20 @@ public final class IoBridge {
public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
- if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) {
- // Linux won't let you bind a link-local address without a scope id. Find one.
- NetworkInterface nif = NetworkInterface.getByInetAddress(address);
- if (nif == null) {
- throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
- }
- try {
- address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
- } catch (UnknownHostException ex) {
- throw new AssertionError(ex); // Can't happen.
+ if (address instanceof Inet6Address) {
+ Inet6Address inet6Address = (Inet6Address) address;
+ if (inet6Address.getScopeId() == 0 && inet6Address.isLinkLocalAddress()) {
+ // Linux won't let you bind a link-local address without a scope id.
+ // Find one.
+ NetworkInterface nif = NetworkInterface.getByInetAddress(address);
+ if (nif == null) {
+ throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
+ }
+ try {
+ address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
+ } catch (UnknownHostException ex) {
+ throw new AssertionError(ex); // Can't happen.
+ }
}
}
try {
@@ -225,6 +229,10 @@ public final class IoBridge {
// Socket options used by java.net but not exposed in SocketOptions.
public static final int JAVA_MCAST_JOIN_GROUP = 19;
public static final int JAVA_MCAST_LEAVE_GROUP = 20;
+ public static final int JAVA_MCAST_JOIN_SOURCE_GROUP = 21;
+ public static final int JAVA_MCAST_LEAVE_SOURCE_GROUP = 22;
+ public static final int JAVA_MCAST_BLOCK_SOURCE = 23;
+ public static final int JAVA_MCAST_UNBLOCK_SOURCE = 24;
public static final int JAVA_IP_MULTICAST_TTL = 17;
/**
@@ -368,16 +376,46 @@ public final class IoBridge {
return;
case IoBridge.JAVA_MCAST_JOIN_GROUP:
case IoBridge.JAVA_MCAST_LEAVE_GROUP:
+ {
StructGroupReq groupReq = (StructGroupReq) value;
int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
return;
+ }
+ case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP:
+ case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP:
+ case IoBridge.JAVA_MCAST_BLOCK_SOURCE:
+ case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE:
+ {
+ StructGroupSourceReq groupSourceReq = (StructGroupSourceReq) value;
+ int level = (groupSourceReq.gsr_group instanceof Inet4Address)
+ ? IPPROTO_IP : IPPROTO_IPV6;
+ int op = getGroupSourceReqOp(option);
+ Libcore.os.setsockoptGroupSourceReq(fd, level, op, groupSourceReq);
+ return;
+ }
default:
throw new SocketException("Unknown socket option: " + option);
}
}
+ private static int getGroupSourceReqOp(int javaValue) {
+ switch (javaValue) {
+ case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP:
+ return MCAST_JOIN_SOURCE_GROUP;
+ case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP:
+ return MCAST_LEAVE_SOURCE_GROUP;
+ case IoBridge.JAVA_MCAST_BLOCK_SOURCE:
+ return MCAST_BLOCK_SOURCE;
+ case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE:
+ return MCAST_UNBLOCK_SOURCE;
+ default:
+ throw new AssertionError(
+ "Unknown java value for setsocketopt op lookup: " + javaValue);
+ }
+ }
+
/**
* java.io only throws FileNotFoundException when opening files, regardless of what actually
* went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 426f2c1..a4541a7 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -117,6 +117,7 @@ public interface Os {
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException;
public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public void setuid(int uid) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java
index 0eb5850..49b0182 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/libcore/io/OsConstants.java
@@ -250,6 +250,10 @@ public final class OsConstants {
public static final int MAP_SHARED = placeholder();
public static final int MCAST_JOIN_GROUP = placeholder();
public static final int MCAST_LEAVE_GROUP = placeholder();
+ public static final int MCAST_JOIN_SOURCE_GROUP = placeholder();
+ public static final int MCAST_LEAVE_SOURCE_GROUP = placeholder();
+ public static final int MCAST_BLOCK_SOURCE = placeholder();
+ public static final int MCAST_UNBLOCK_SOURCE = placeholder();
public static final int MCL_CURRENT = placeholder();
public static final int MCL_FUTURE = placeholder();
public static final int MSG_CTRUNC = placeholder();
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index 35d3784..c6f13ea 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -168,6 +168,7 @@ public final class Posix implements Os {
public native void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public native void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
public native void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public native void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException;
public native void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
public native void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public native void setuid(int uid) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/StructGroupSourceReq.java b/luni/src/main/java/libcore/io/StructGroupSourceReq.java
new file mode 100644
index 0000000..3ac94b8
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructGroupSourceReq.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+import java.net.InetAddress;
+
+/**
+ * Corresponds to C's {@code struct group_source_req}.
+ */
+public final class StructGroupSourceReq {
+
+ public final int gsr_interface;
+
+ public final InetAddress gsr_group;
+
+ public final InetAddress gsr_source;
+
+ public StructGroupSourceReq(int gsr_interface, InetAddress gsr_group, InetAddress gsr_source) {
+ this.gsr_interface = gsr_interface;
+ this.gsr_group = gsr_group;
+ this.gsr_source = gsr_source;
+ }
+
+ @Override
+ public String toString() {
+ return "StructGroupSourceReq[gsr_interface=" + gsr_interface + ",gsr_group=" + gsr_group
+ + ",gsr_source=" + gsr_source + "]";
+ }
+}
diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h
index 28e9fa5..6b720d4 100644
--- a/luni/src/main/native/NetworkUtilities.h
+++ b/luni/src/main/native/NetworkUtilities.h
@@ -35,7 +35,7 @@ bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port,
// An Inet4Address will be converted to a sockaddr_in. This is probably only useful for
// getnameinfo(2), where we'll be presenting the result to the user and the user may actually
// care whether the original address was pure IPv4 or an IPv4-mapped IPv6 address, and
-// for the MCAST_JOIN_GROUP socket option.
+// for the MCAST_JOIN_GROUP, MCAST_LEAVE_GROUP, and other multicast socket options.
bool inetAddressToSockaddrVerbatim(JNIEnv* env, jobject inetAddress, int port,
sockaddr_storage& ss, socklen_t& sa_len);
diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp
index f9e9eed..cae1703 100644
--- a/luni/src/main/native/libcore_io_OsConstants.cpp
+++ b/luni/src/main/native/libcore_io_OsConstants.cpp
@@ -310,6 +310,18 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) {
#if defined(MCAST_LEAVE_GROUP)
initConstant(env, c, "MCAST_LEAVE_GROUP", MCAST_LEAVE_GROUP);
#endif
+#if defined(MCAST_JOIN_SOURCE_GROUP)
+ initConstant(env, c, "MCAST_JOIN_SOURCE_GROUP", MCAST_JOIN_SOURCE_GROUP);
+#endif
+#if defined(MCAST_LEAVE_SOURCE_GROUP)
+ initConstant(env, c, "MCAST_LEAVE_SOURCE_GROUP", MCAST_LEAVE_SOURCE_GROUP);
+#endif
+#if defined(MCAST_BLOCK_SOURCE)
+ initConstant(env, c, "MCAST_BLOCK_SOURCE", MCAST_BLOCK_SOURCE);
+#endif
+#if defined(MCAST_UNBLOCK_SOURCE)
+ initConstant(env, c, "MCAST_UNBLOCK_SOURCE", MCAST_UNBLOCK_SOURCE);
+#endif
initConstant(env, c, "MCL_CURRENT", MCL_CURRENT);
initConstant(env, c, "MCL_FUTURE", MCL_FUTURE);
initConstant(env, c, "MSG_CTRUNC", MSG_CTRUNC);
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 1f38a57..b9f16a7 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -1193,6 +1193,7 @@ static void Posix_setsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level
// Mac OS didn't support modern multicast APIs until 10.7.
static void Posix_setsockoptIpMreqn(JNIEnv*, jobject, jobject, jint, jint, jint) { abort(); }
static void Posix_setsockoptGroupReq(JNIEnv*, jobject, jobject, jint, jint, jobject) { abort(); }
+static void Posix_setsockoptGroupSourceReq(JNIEnv*, jobject, jobject, jint, jint, jobject) { abort(); }
#else
static void Posix_setsockoptIpMreqn(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
ip_mreqn req;
@@ -1221,6 +1222,7 @@ static void Posix_setsockoptGroupReq(JNIEnv* env, jobject, jobject javaFd, jint
if (rc == -1 && errno == EINVAL) {
// Maybe we're a 32-bit binary talking to a 64-bit kernel?
// glibc doesn't automatically handle this.
+ // http://sourceware.org/bugzilla/show_bug.cgi?id=12080
struct group_req64 {
uint32_t gr_interface;
uint32_t my_padding;
@@ -1233,6 +1235,48 @@ static void Posix_setsockoptGroupReq(JNIEnv* env, jobject, jobject javaFd, jint
}
throwIfMinusOne(env, "setsockopt", rc);
}
+
+static void Posix_setsockoptGroupSourceReq(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaGroupSourceReq) {
+ socklen_t sa_len;
+ struct group_source_req req;
+ memset(&req, 0, sizeof(req));
+
+ static jfieldID gsrInterfaceFid = env->GetFieldID(JniConstants::structGroupSourceReqClass, "gsr_interface", "I");
+ req.gsr_interface = env->GetIntField(javaGroupSourceReq, gsrInterfaceFid);
+ // Get the IPv4 or IPv6 multicast address to join or leave.
+ static jfieldID gsrGroupFid = env->GetFieldID(JniConstants::structGroupSourceReqClass, "gsr_group", "Ljava/net/InetAddress;");
+ ScopedLocalRef<jobject> javaGroup(env, env->GetObjectField(javaGroupSourceReq, gsrGroupFid));
+ if (!inetAddressToSockaddrVerbatim(env, javaGroup.get(), 0, req.gsr_group, sa_len)) {
+ return;
+ }
+
+ // Get the IPv4 or IPv6 multicast address to add to the filter.
+ static jfieldID gsrSourceFid = env->GetFieldID(JniConstants::structGroupSourceReqClass, "gsr_source", "Ljava/net/InetAddress;");
+ ScopedLocalRef<jobject> javaSource(env, env->GetObjectField(javaGroupSourceReq, gsrSourceFid));
+ if (!inetAddressToSockaddrVerbatim(env, javaSource.get(), 0, req.gsr_source, sa_len)) {
+ return;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req, sizeof(req)));
+ if (rc == -1 && errno == EINVAL) {
+ // Maybe we're a 32-bit binary talking to a 64-bit kernel?
+ // glibc doesn't automatically handle this.
+ // http://sourceware.org/bugzilla/show_bug.cgi?id=12080
+ struct group_source_req64 {
+ uint32_t gsr_interface;
+ uint32_t my_padding;
+ sockaddr_storage gsr_group;
+ sockaddr_storage gsr_source;
+ };
+ group_source_req64 req64;
+ req64.gsr_interface = req.gsr_interface;
+ memcpy(&req64.gsr_group, &req.gsr_group, sizeof(req.gsr_group));
+ memcpy(&req64.gsr_source, &req.gsr_source, sizeof(req.gsr_source));
+ rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req64, sizeof(req64)));
+ }
+ throwIfMinusOne(env, "setsockopt", rc);
+}
#endif
static void Posix_setsockoptLinger(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaLinger) {
@@ -1471,6 +1515,7 @@ static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Posix, setsockoptInt, "(Ljava/io/FileDescriptor;III)V"),
NATIVE_METHOD(Posix, setsockoptIpMreqn, "(Ljava/io/FileDescriptor;III)V"),
NATIVE_METHOD(Posix, setsockoptGroupReq, "(Ljava/io/FileDescriptor;IILlibcore/io/StructGroupReq;)V"),
+ NATIVE_METHOD(Posix, setsockoptGroupSourceReq, "(Ljava/io/FileDescriptor;IILlibcore/io/StructGroupSourceReq;)V"),
NATIVE_METHOD(Posix, setsockoptLinger, "(Ljava/io/FileDescriptor;IILlibcore/io/StructLinger;)V"),
NATIVE_METHOD(Posix, setsockoptTimeval, "(Ljava/io/FileDescriptor;IILlibcore/io/StructTimeval;)V"),
NATIVE_METHOD(Posix, setuid, "(I)V"),
diff --git a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelMulticastTest.java b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelMulticastTest.java
new file mode 100644
index 0000000..9882af5
--- /dev/null
+++ b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelMulticastTest.java
@@ -0,0 +1,1073 @@
+package libcore.java.nio.channels;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.StandardSocketOptions;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.MembershipKey;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Tests associated with multicast behavior of DatagramChannel.
+ */
+public class DatagramChannelMulticastTest extends TestCase {
+
+ private static InetAddress lookup(String s) {
+ try {
+ return InetAddress.getByName(s);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // These IP addresses aren't inherently "good" or "bad"; they're just used like that.
+ // We use the "good" addresses for our actual group, and the "bad" addresses are for
+ // a group that we won't actually set up.
+ private static final InetAddress GOOD_MULTICAST_IPv4 = lookup("239.255.0.1");
+ private static final InetAddress BAD_MULTICAST_IPv4 = lookup("239.255.0.2");
+ private static final InetAddress GOOD_MULTICAST_IPv6 = lookup("ff05::7:7");
+ private static final InetAddress BAD_MULTICAST_IPv6 = lookup("ff05::7:8");
+
+ // Special addresses.
+ private static final InetAddress WILDCARD_IPv4 = lookup("0.0.0.0");
+ private static final InetAddress WILDCARD_IPv6 = lookup("::");
+
+ // Arbitrary unicast addresses. Used when the value doesn't actually matter. e.g. for source
+ // filters.
+ private static final InetAddress UNICAST_IPv4_1 = lookup("192.168.1.1");
+ private static final InetAddress UNICAST_IPv4_2 = lookup("192.168.1.2");
+ private static final InetAddress UNICAST_IPv6_1 = lookup("2001:db8::1");
+ private static final InetAddress UNICAST_IPv6_2 = lookup("2001:db8::2");
+
+ private NetworkInterface networkInterface1;
+ private NetworkInterface IPV6networkInterface1;
+ private NetworkInterface loopbackInterface;
+
+ @Override
+ protected void setUp() throws Exception {
+ // The loopback interface isn't actually useful for sending/receiving multicast messages
+ // but it can be used as a dummy for tests where that does not matter.
+ loopbackInterface = NetworkInterface.getByInetAddress(InetAddress.getLoopbackAddress());
+ assertNotNull(loopbackInterface);
+ assertTrue(loopbackInterface.isLoopback());
+ assertFalse(loopbackInterface.supportsMulticast());
+
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+
+ // only consider interfaces that have addresses associated with them.
+ // Otherwise tests don't work so well
+ if (interfaces != null) {
+ boolean atLeastOneInterface = false;
+ while (interfaces.hasMoreElements() && (atLeastOneInterface == false)) {
+ networkInterface1 = interfaces.nextElement();
+ if (willWorkForMulticast(networkInterface1)) {
+ atLeastOneInterface = true;
+ }
+ }
+
+ assertTrue("Test environment must have at least one environment capable of multicast",
+ atLeastOneInterface);
+
+ // Find the first multicast-compatible interface that supports IPV6 if one exists
+ interfaces = NetworkInterface.getNetworkInterfaces();
+
+ boolean found = false;
+ while (interfaces.hasMoreElements() && !found) {
+ NetworkInterface nextInterface = interfaces.nextElement();
+ if (willWorkForMulticast(nextInterface)) {
+ Enumeration<InetAddress> addresses = nextInterface.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ final InetAddress nextAddress = addresses.nextElement();
+ if (nextAddress instanceof Inet6Address) {
+ IPV6networkInterface1 = nextInterface;
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void test_open() throws IOException {
+ DatagramChannel dc = DatagramChannel.open();
+
+ // Unlike MulticastSocket, DatagramChannel has SO_REUSEADDR set to false by default.
+ assertFalse(dc.getOption(StandardSocketOptions.SO_REUSEADDR));
+
+ assertNull(dc.getLocalAddress());
+ assertTrue(dc.isOpen());
+ assertFalse(dc.isConnected());
+ }
+
+ public void test_bind_null() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ assertNotNull(dc.getLocalAddress());
+ assertTrue(dc.isOpen());
+ assertFalse(dc.isConnected());
+
+ dc.close();
+ try {
+ dc.getLocalAddress();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+ assertFalse(dc.isOpen());
+ assertFalse(dc.isConnected());
+ }
+
+ public void test_joinAnySource_afterClose() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ dc.close();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+ }
+
+ public void test_joinAnySource_nullGroupAddress() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(null, networkInterface1);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinAnySource_nullNetworkInterface() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinAnySource_nonMulticastGroupAddress_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(UNICAST_IPv4_1, networkInterface1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinAnySource_nonMulticastGroupAddress_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(UNICAST_IPv6_1, networkInterface1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinAnySource_IPv4() throws Exception {
+ test_joinAnySource(GOOD_MULTICAST_IPv4, BAD_MULTICAST_IPv4);
+ }
+
+ public void test_joinAnySource_IPv6() throws Exception {
+ test_joinAnySource(GOOD_MULTICAST_IPv6, BAD_MULTICAST_IPv6);
+ }
+
+ private void test_joinAnySource(InetAddress group, InetAddress group2) throws Exception {
+ // Set up a receiver join the group on networkInterface1
+ DatagramChannel receiverChannel = createReceiverChannel();
+ InetSocketAddress localAddress = (InetSocketAddress) receiverChannel.getLocalAddress();
+ receiverChannel.join(group, networkInterface1);
+
+ String msg = "Hello World";
+ sendMulticastMessage(group, localAddress.getPort(), msg);
+
+ // now verify that we received the data as expected
+ ByteBuffer recvBuffer = ByteBuffer.allocate(100);
+ SocketAddress sourceAddress = receiverChannel.receive(recvBuffer);
+ assertNotNull(sourceAddress);
+ assertEquals(msg, new String(recvBuffer.array(), 0, recvBuffer.position()));
+
+ // now verify that we didn't receive the second message
+ String msg2 = "Hello World - Different Group";
+ sendMulticastMessage(group2, localAddress.getPort(), msg2);
+ recvBuffer.position(0);
+ SocketAddress sourceAddress2 = receiverChannel.receive(recvBuffer);
+ assertNull(sourceAddress2);
+
+ receiverChannel.close();
+ }
+
+ public void test_joinAnySource_processLimit() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ for (byte i = 1; i <= 25; i++) {
+ InetAddress groupAddress = Inet4Address.getByName("239.255.0." + i);
+ try {
+ dc.join(groupAddress, networkInterface1);
+ } catch (SocketException e) {
+ // There is a limit, that's ok according to the RI docs. For this test a lower bound of 20
+ // is used, which appears to be the default linux limit.
+ // See /proc/sys/net/ipv4/igmp_max_memberships
+ assertTrue(i > 20);
+ break;
+ }
+ }
+
+ dc.close();
+ }
+
+ public void test_joinAnySource_blockLimit() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey key = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ for (byte i = 1; i <= 15; i++) {
+ InetAddress sourceAddress = Inet4Address.getByName("10.0.0." + i);
+ try {
+ key.block(sourceAddress);
+ } catch (SocketException e) {
+ // There is a limit, that's ok according to the RI docs. For this test a lower bound of 10
+ // is used, which appears to be the default linux limit.
+ // See /proc/sys/net/ipv4/igmp_max_msf
+ assertTrue(i > 10);
+ break;
+ }
+ }
+
+ dc.close();
+ }
+
+ /** Confirms that calling join() does not cause an implicit bind() to take place. */
+ public void test_joinAnySource_doesNotCauseBind() throws Exception {
+ DatagramChannel dc = DatagramChannel.open();
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ assertNull(dc.getLocalAddress());
+
+ dc.close();
+ }
+
+ public void test_joinAnySource_networkInterfaces() throws Exception {
+ // Check that we can join on specific interfaces and that we only receive if data is
+ // received on that interface. This test is only really useful on devices with multiple
+ // non-loopback interfaces.
+
+ ArrayList<NetworkInterface> realInterfaces = new ArrayList<NetworkInterface>();
+ Enumeration<NetworkInterface> theInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (theInterfaces.hasMoreElements()) {
+ NetworkInterface thisInterface = theInterfaces.nextElement();
+ if (thisInterface.getInetAddresses().hasMoreElements()) {
+ realInterfaces.add(thisInterface);
+ }
+ }
+
+ for (int i = 0; i < realInterfaces.size(); i++) {
+ NetworkInterface thisInterface = realInterfaces.get(i);
+ if (!thisInterface.supportsMulticast()) {
+ // Skip interfaces that do not support multicast - there's no point in proving
+ // they cannot send / receive multicast messages.
+ continue;
+ }
+
+ // get the first address on the interface
+
+ // start server which is joined to the group and has
+ // only asked for packets on this interface
+ Enumeration<InetAddress> addresses = thisInterface.getInetAddresses();
+
+ NetworkInterface sendingInterface = null;
+ InetAddress group = null;
+ if (addresses.hasMoreElements()) {
+ InetAddress firstAddress = addresses.nextElement();
+ if (firstAddress instanceof Inet4Address) {
+ group = GOOD_MULTICAST_IPv4;
+ sendingInterface = networkInterface1;
+ } else {
+ // if this interface only seems to support IPV6 addresses
+ group = GOOD_MULTICAST_IPv6;
+ sendingInterface = IPV6networkInterface1;
+ }
+ }
+
+ DatagramChannel dc = createReceiverChannel();
+ InetSocketAddress localAddress = (InetSocketAddress) dc.getLocalAddress();
+ dc.join(group, thisInterface);
+
+ // Now send out a package on sendingInterface. We should only see the packet if we send
+ // it on the same interface we are listening on (thisInterface).
+ String msg = "Hello World - Again" + thisInterface.getName();
+ sendMulticastMessage(group, localAddress.getPort(), msg, sendingInterface);
+
+ ByteBuffer recvBuffer = ByteBuffer.allocate(100);
+ SocketAddress sourceAddress = dc.receive(recvBuffer);
+ if (thisInterface.equals(sendingInterface)) {
+ assertEquals(msg, new String(recvBuffer.array(), 0, recvBuffer.position()));
+ } else {
+ assertNull(sourceAddress);
+ }
+
+ dc.close();
+ }
+ }
+
+ /** Confirms that the scope of each membership is network interface-level. */
+ public void test_join_canMixTypesOnDifferentInterfaces() throws Exception {
+ DatagramChannel dc = DatagramChannel.open();
+ MembershipKey membershipKey1 = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ MembershipKey membershipKey2 = dc.join(GOOD_MULTICAST_IPv4, loopbackInterface, UNICAST_IPv4_1);
+ assertNotSame(membershipKey1, membershipKey2);
+
+ dc.close();
+ }
+
+
+ private DatagramChannel createReceiverChannel() throws Exception {
+ DatagramChannel dc = DatagramChannel.open();
+ dc.bind(null /* leave the OS to determine the port, and use the wildcard address */);
+ configureChannelForReceiving(dc);
+ return dc;
+ }
+
+ public void test_joinAnySource_multiple_joins_IPv4()
+ throws Exception {
+ test_joinAnySource_multiple_joins(GOOD_MULTICAST_IPv4);
+ }
+
+ public void test_joinAnySource_multiple_joins_IPv6()
+ throws Exception {
+ test_joinAnySource_multiple_joins(GOOD_MULTICAST_IPv6);
+ }
+
+ private void test_joinAnySource_multiple_joins(InetAddress group) throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+
+ MembershipKey membershipKey1 = dc.join(group, networkInterface1);
+
+ MembershipKey membershipKey2 = dc.join(group, loopbackInterface);
+ assertFalse(membershipKey1.equals(membershipKey2));
+
+ MembershipKey membershipKey1_2 = dc.join(group, networkInterface1);
+ assertEquals(membershipKey1, membershipKey1_2);
+
+ dc.close();
+ }
+
+ public void test_joinAnySource_multicastLoopOption_IPv4() throws Exception {
+ test_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv4);
+ }
+
+ public void test_multicastLoopOption_IPv6() throws Exception {
+ test_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv6);
+ }
+
+ private void test_joinAnySource_multicastLoopOption(InetAddress group) throws Exception {
+ final String message = "Hello, world!";
+
+ DatagramChannel dc = createReceiverChannel();
+ dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, true /* enable loop */);
+ configureChannelForReceiving(dc);
+ dc.join(group, networkInterface1);
+
+ InetSocketAddress localAddress = (InetSocketAddress) dc.getLocalAddress();
+
+ // send the datagram
+ byte[] sendData = message.getBytes();
+ ByteBuffer sendBuffer = ByteBuffer.wrap(sendData);
+ dc.send(sendBuffer, new InetSocketAddress(group, localAddress.getPort()));
+
+ // receive the datagram
+ ByteBuffer recvBuffer = ByteBuffer.allocate(100);
+ SocketAddress sourceAddress = dc.receive(recvBuffer);
+ assertNotNull(sourceAddress);
+
+ String recvMessage = new String(recvBuffer.array(), 0, recvBuffer.position());
+ assertEquals(message, recvMessage);
+
+ // Turn off loop
+ dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, false /* enable loopback */);
+
+ // send another datagram
+ recvBuffer.position(0);
+ ByteBuffer sendBuffer2 = ByteBuffer.wrap(sendData);
+ dc.send(sendBuffer2, new InetSocketAddress(group, localAddress.getPort()));
+
+ SocketAddress sourceAddress2 = dc.receive(recvBuffer);
+ assertNull(sourceAddress2);
+
+ dc.close();
+ }
+
+ public void testMembershipKeyAccessors_IPv4() throws Exception {
+ testMembershipKeyAccessors(GOOD_MULTICAST_IPv4);
+ }
+
+ public void testMembershipKeyAccessors_IPv6() throws Exception {
+ testMembershipKeyAccessors(GOOD_MULTICAST_IPv6);
+ }
+
+ private void testMembershipKeyAccessors(InetAddress group) throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+
+ MembershipKey key = dc.join(group, networkInterface1);
+ assertSame(dc, key.channel());
+ assertSame(group, key.group());
+ assertTrue(key.isValid());
+ assertSame(networkInterface1, key.networkInterface());
+ assertNull(key.sourceAddress());
+ }
+
+ public void test_dropAnySource_twice_IPv4() throws Exception {
+ test_dropAnySource_twice(GOOD_MULTICAST_IPv4);
+ }
+
+ public void test_dropAnySource_twice_IPv6() throws Exception {
+ test_dropAnySource_twice(GOOD_MULTICAST_IPv6);
+ }
+
+ private void test_dropAnySource_twice(InetAddress group) throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(group, networkInterface1);
+
+ assertTrue(membershipKey.isValid());
+ membershipKey.drop();
+ assertFalse(membershipKey.isValid());
+
+ // Try to leave a group we are no longer a member of - should do nothing.
+ membershipKey.drop();
+
+ dc.close();
+ }
+
+ public void test_close_invalidatesMembershipKey() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+
+ assertTrue(membershipKey.isValid());
+
+ dc.close();
+
+ assertFalse(membershipKey.isValid());
+ }
+
+ public void test_block_null() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ try {
+ membershipKey.block(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_block_mixedAddressTypes_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ try {
+ membershipKey.block(UNICAST_IPv6_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_block_mixedAddressTypes_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv6, networkInterface1);
+ try {
+ membershipKey.block(UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_block_cannotBlockWithSourceSpecificMembership() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1, UNICAST_IPv4_1);
+ try {
+ membershipKey.block(UNICAST_IPv4_2);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_block_multipleBlocksIgnored() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ membershipKey.block(UNICAST_IPv4_1);
+
+ MembershipKey membershipKey2 = membershipKey.block(UNICAST_IPv4_1);
+ assertSame(membershipKey2, membershipKey);
+
+ dc.close();
+ }
+
+ public void test_block_wildcardAddress() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ try {
+ membershipKey.block(WILDCARD_IPv4);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_unblock_multipleUnblocksFail() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+
+ try {
+ membershipKey.unblock(UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ assertTrue(membershipKey.isValid());
+
+ membershipKey.block(UNICAST_IPv4_1);
+ membershipKey.unblock(UNICAST_IPv4_1);
+
+ try {
+ membershipKey.unblock(UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ dc.close();
+ }
+
+ public void test_unblock_null() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ membershipKey.block(UNICAST_IPv4_1);
+
+ try {
+ membershipKey.unblock(null);
+ fail();
+ } catch (IllegalStateException expected) {
+ // Either of these exceptions are fine
+ } catch (NullPointerException expected) {
+ // Either of these exception are fine
+ }
+
+ dc.close();
+ }
+
+ public void test_unblock_mixedAddressTypes_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1);
+ try {
+ membershipKey.unblock(UNICAST_IPv6_1);
+ fail();
+ } catch (IllegalStateException expected) {
+ // Either of these exceptions are fine
+ } catch (IllegalArgumentException expected) {
+ // Either of these exceptions are fine
+ }
+
+ dc.close();
+ }
+
+ public void test_unblock_mixedAddressTypes_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv6, networkInterface1);
+ try {
+ membershipKey.unblock(UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalStateException expected) {
+ // Either of these exceptions are fine
+ } catch (IllegalArgumentException expected) {
+ // Either of these exceptions are fine
+ }
+
+ dc.close();
+ }
+
+ /** Checks that block() works when the receiver is bound to the multicast group address */
+ public void test_block_filtersAsExpected_groupBind_ipv4() throws Exception {
+ InetAddress ipv4LocalAddress = getLocalIpv4Address(networkInterface1);
+ test_block_filtersAsExpected(
+ ipv4LocalAddress /* senderBindAddress */,
+ GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv4 /* groupAddress */);
+ }
+
+ /** Checks that block() works when the receiver is bound to the multicast group address */
+ public void test_block_filtersAsExpected_groupBind_ipv6() throws Exception {
+ InetAddress ipv6LocalAddress = getLocalIpv6Address(IPV6networkInterface1);
+ test_block_filtersAsExpected(
+ ipv6LocalAddress /* senderBindAddress */,
+ GOOD_MULTICAST_IPv6 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv6 /* groupAddress */);
+ }
+
+ /** Checks that block() works when the receiver is bound to the "any" address */
+ public void test_block_filtersAsExpected_anyBind_ipv4() throws Exception {
+ InetAddress ipv4LocalAddress = getLocalIpv4Address(networkInterface1);
+ test_block_filtersAsExpected(
+ ipv4LocalAddress /* senderBindAddress */,
+ WILDCARD_IPv4 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv4 /* groupAddress */);
+ }
+
+ /** Checks that block() works when the receiver is bound to the "any" address */
+ public void test_block_filtersAsExpected_anyBind_ipv6() throws Exception {
+ InetAddress ipv6LocalAddress = getLocalIpv6Address(IPV6networkInterface1);
+ test_block_filtersAsExpected(
+ ipv6LocalAddress /* senderBindAddress */,
+ WILDCARD_IPv6 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv6 /* groupAddress */);
+ }
+
+ private void test_block_filtersAsExpected(
+ InetAddress senderBindAddress, InetAddress receiverBindAddress, InetAddress groupAddress)
+ throws Exception {
+
+ DatagramChannel sendingChannel = DatagramChannel.open();
+ // In order to block a sender the sender's address must be known. The sendingChannel is
+ // explicitly bound to a known, non-loopback address.
+ sendingChannel.bind(new InetSocketAddress(senderBindAddress, 0));
+ InetSocketAddress sendingAddress = (InetSocketAddress) sendingChannel.getLocalAddress();
+
+ DatagramChannel receivingChannel = DatagramChannel.open();
+ configureChannelForReceiving(receivingChannel);
+ receivingChannel.bind(
+ new InetSocketAddress(receiverBindAddress, 0) /* local port left to the OS to determine */);
+ InetSocketAddress localReceivingAddress =
+ (InetSocketAddress) receivingChannel.getLocalAddress();
+ InetSocketAddress groupSocketAddress =
+ new InetSocketAddress(groupAddress, localReceivingAddress.getPort());
+ MembershipKey membershipKey =
+ receivingChannel.join(groupSocketAddress.getAddress(), networkInterface1);
+
+ ByteBuffer receiveBuffer = ByteBuffer.allocate(10);
+
+ // Send a message. It should be received.
+ String msg1 = "Hello1";
+ sendMessage(sendingChannel, msg1, groupSocketAddress);
+ InetSocketAddress sourceAddress1 = (InetSocketAddress) receivingChannel.receive(receiveBuffer);
+ assertEquals(sourceAddress1, sendingAddress);
+ assertEquals(msg1, new String(receiveBuffer.array(), 0, receiveBuffer.position()));
+
+ // Now block the sender
+ membershipKey.block(sendingAddress.getAddress());
+
+ // Send a message. It should be filtered.
+ String msg2 = "Hello2";
+ sendMessage(sendingChannel, msg2, groupSocketAddress);
+ receiveBuffer.position(0);
+ InetSocketAddress sourceAddress2 = (InetSocketAddress) receivingChannel.receive(receiveBuffer);
+ assertNull(sourceAddress2);
+
+ // Now unblock the sender
+ membershipKey.unblock(sendingAddress.getAddress());
+
+ // Send a message. It should be received.
+ String msg3 = "Hello3";
+ sendMessage(sendingChannel, msg3, groupSocketAddress);
+ receiveBuffer.position(0);
+ InetSocketAddress sourceAddress3 = (InetSocketAddress) receivingChannel.receive(receiveBuffer);
+ assertEquals(sourceAddress3, sendingAddress);
+ assertEquals(msg3, new String(receiveBuffer.array(), 0, receiveBuffer.position()));
+
+ sendingChannel.close();
+ receivingChannel.close();
+ }
+
+ public void test_joinSourceSpecific_nullGroupAddress() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(null, networkInterface1, UNICAST_IPv4_1);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_afterClose() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ dc.close();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1, UNICAST_IPv4_1);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+ }
+
+ public void test_joinSourceSpecific_nullNetworkInterface() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, null, UNICAST_IPv4_1);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nonMulticastGroupAddress_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(UNICAST_IPv4_1, networkInterface1, UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nonMulticastGroupAddress_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(UNICAST_IPv6_1, IPV6networkInterface1, UNICAST_IPv6_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nullSourceAddress_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nullSourceAddress_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv6, networkInterface1, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_mixedAddressTypes() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1, UNICAST_IPv6_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ dc.join(GOOD_MULTICAST_IPv6, networkInterface1, UNICAST_IPv4_1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nonUnicastSourceAddress_IPv4() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1, BAD_MULTICAST_IPv4);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_nonUniicastSourceAddress_IPv6() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ try {
+ dc.join(GOOD_MULTICAST_IPv6, networkInterface1, BAD_MULTICAST_IPv6);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ dc.close();
+ }
+
+ public void test_joinSourceSpecific_multipleSourceAddressLimit() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ for (byte i = 1; i <= 20; i++) {
+ InetAddress sourceAddress = Inet4Address.getByAddress(new byte[] { 10, 0, 0, i});
+ try {
+ dc.join(GOOD_MULTICAST_IPv4, networkInterface1, sourceAddress);
+ } catch (SocketException e) {
+ // There is a limit, that's ok according to the RI docs. For this test a lower bound of 10
+ // is used, which appears to be the default linux limit. See /proc/sys/net/ipv4/igmp_max_msf
+ assertTrue(i > 10);
+ break;
+ }
+ }
+
+ dc.close();
+ }
+
+ /**
+ * Checks that a source-specific join() works when the receiver is bound to the multicast group
+ * address
+ */
+ public void test_joinSourceSpecific_null() throws Exception {
+ InetAddress ipv4LocalAddress = getLocalIpv4Address(networkInterface1);
+ test_joinSourceSpecific(
+ ipv4LocalAddress /* senderBindAddress */,
+ GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv4 /* groupAddress */,
+ UNICAST_IPv4_1 /* badSenderAddress */);
+ }
+
+ /**
+ * Checks that a source-specific join() works when the receiver is bound to the multicast group
+ * address
+ */
+ public void test_joinSourceSpecific_groupBind_ipv4() throws Exception {
+ InetAddress ipv4LocalAddress = getLocalIpv4Address(networkInterface1);
+ test_joinSourceSpecific(
+ ipv4LocalAddress /* senderBindAddress */,
+ GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv4 /* groupAddress */,
+ UNICAST_IPv4_1 /* badSenderAddress */);
+ }
+
+ /**
+ * Checks that a source-specific join() works when the receiver is bound to the multicast group
+ * address
+ */
+ public void test_joinSourceSpecific_groupBind_ipv6() throws Exception {
+ InetAddress ipv6LocalAddress = getLocalIpv6Address(IPV6networkInterface1);
+ test_joinSourceSpecific(
+ ipv6LocalAddress /* senderBindAddress */,
+ GOOD_MULTICAST_IPv6 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv6 /* groupAddress */,
+ UNICAST_IPv6_1 /* badSenderAddress */);
+ }
+
+ /** Checks that a source-specific join() works when the receiver is bound to the "any" address */
+ public void test_joinSourceSpecific_anyBind_ipv4() throws Exception {
+ InetAddress ipv4LocalAddress = getLocalIpv4Address(networkInterface1);
+ test_joinSourceSpecific(
+ ipv4LocalAddress /* senderBindAddress */,
+ WILDCARD_IPv4 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv4 /* groupAddress */,
+ UNICAST_IPv4_1 /* badSenderAddress */);
+ }
+
+ /** Checks that a source-specific join() works when the receiver is bound to the "any" address */
+ public void test_joinSourceSpecific_anyBind_ipv6() throws Exception {
+ InetAddress ipv6LocalAddress = getLocalIpv6Address(IPV6networkInterface1);
+ test_joinSourceSpecific(
+ ipv6LocalAddress /* senderBindAddress */,
+ WILDCARD_IPv6 /* receiverBindAddress */,
+ GOOD_MULTICAST_IPv6 /* groupAddress */,
+ UNICAST_IPv6_1 /* badSenderAddress */);
+ }
+
+ /**
+ * Checks that the source-specific membership is correctly source-filtering.
+ *
+ * @param senderBindAddress the address to bind the sender socket to
+ * @param receiverBindAddress the address to bind the receiver socket to
+ * @param groupAddress the group address to join
+ * @param badSenderAddress a unicast address to join to perform a negative test
+ */
+ private void test_joinSourceSpecific(
+ InetAddress senderBindAddress, InetAddress receiverBindAddress, InetAddress groupAddress,
+ InetAddress badSenderAddress)
+ throws Exception {
+ DatagramChannel sendingChannel = DatagramChannel.open();
+ // In order to be source-specific the sender's address must be known. The sendingChannel is
+ // explicitly bound to a known, non-loopback address.
+ sendingChannel.bind(new InetSocketAddress(senderBindAddress, 0));
+ InetSocketAddress sendingAddress = (InetSocketAddress) sendingChannel.getLocalAddress();
+
+ DatagramChannel receivingChannel = DatagramChannel.open();
+ receivingChannel.bind(
+ new InetSocketAddress(receiverBindAddress, 0) /* local port left to the OS to determine */);
+ configureChannelForReceiving(receivingChannel);
+
+ InetSocketAddress localReceivingAddress =
+ (InetSocketAddress) receivingChannel.getLocalAddress();
+ InetSocketAddress groupSocketAddress =
+ new InetSocketAddress(groupAddress, localReceivingAddress.getPort());
+ MembershipKey membershipKey1 = receivingChannel
+ .join(groupSocketAddress.getAddress(), networkInterface1, senderBindAddress);
+
+ ByteBuffer receiveBuffer = ByteBuffer.allocate(10);
+
+ // Send a message. It should be received.
+ String msg1 = "Hello1";
+ sendMessage(sendingChannel, msg1, groupSocketAddress);
+ InetSocketAddress sourceAddress1 = (InetSocketAddress) receivingChannel.receive(receiveBuffer);
+ assertEquals(sourceAddress1, sendingAddress);
+ assertEquals(msg1, new String(receiveBuffer.array(), 0, receiveBuffer.position()));
+
+ membershipKey1.drop();
+
+ receivingChannel.join(groupSocketAddress.getAddress(), networkInterface1, badSenderAddress);
+
+ // Send a message. It should not be received.
+ String msg2 = "Hello2";
+ sendMessage(sendingChannel, msg2, groupSocketAddress);
+ InetSocketAddress sourceAddress2 = (InetSocketAddress) receivingChannel.receive(receiveBuffer);
+ assertNull(sourceAddress2);
+
+ receivingChannel.close();
+ sendingChannel.close();
+ }
+
+ public void test_dropSourceSpecific_twice_IPv4() throws Exception {
+ test_dropSourceSpecific_twice(
+ GOOD_MULTICAST_IPv4 /* groupAddress */, UNICAST_IPv4_1 /* sourceAddress */);
+ }
+
+ public void test_dropSourceSpecific_twice_IPv6() throws Exception {
+ test_dropSourceSpecific_twice(
+ GOOD_MULTICAST_IPv6 /* groupAddress */, UNICAST_IPv6_1 /* sourceAddress */);
+ }
+
+ private void test_dropSourceSpecific_twice(InetAddress groupAddress, InetAddress sourceAddress)
+ throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(groupAddress, networkInterface1, sourceAddress);
+
+ assertTrue(membershipKey.isValid());
+ membershipKey.drop();
+ assertFalse(membershipKey.isValid());
+
+ // Try to leave a group we are no longer a member of - should do nothing.
+ membershipKey.drop();
+
+ dc.close();
+ }
+
+ public void test_dropSourceSpecific_sourceKeysAreIndependent_IPv4() throws Exception {
+ test_dropSourceSpecific_sourceKeysAreIndependent(
+ GOOD_MULTICAST_IPv4 /* groupAddress */,
+ UNICAST_IPv4_1 /* sourceAddress1 */,
+ UNICAST_IPv4_2 /* sourceAddress2 */);
+ }
+
+ public void test_dropSourceSpecific_sourceKeysAreIndependent_IPv6() throws Exception {
+ test_dropSourceSpecific_sourceKeysAreIndependent(
+ GOOD_MULTICAST_IPv6 /* groupAddress */,
+ UNICAST_IPv6_1 /* sourceAddress1 */,
+ UNICAST_IPv6_2 /* sourceAddress2 */);
+ }
+
+ private void test_dropSourceSpecific_sourceKeysAreIndependent(
+ InetAddress groupAddress, InetAddress sourceAddress1, InetAddress sourceAddress2)
+ throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey1 = dc.join(groupAddress, networkInterface1, sourceAddress1);
+ MembershipKey membershipKey2 = dc.join(groupAddress, networkInterface1, sourceAddress2);
+ assertFalse(membershipKey1.equals(membershipKey2));
+ assertTrue(membershipKey1.isValid());
+ assertTrue(membershipKey2.isValid());
+
+ membershipKey1.drop();
+
+ assertFalse(membershipKey1.isValid());
+ assertTrue(membershipKey2.isValid());
+
+ dc.close();
+ }
+
+ public void test_drop_keyBehaviorAfterDrop() throws Exception {
+ DatagramChannel dc = createReceiverChannel();
+ MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, networkInterface1, UNICAST_IPv4_1);
+ membershipKey.drop();
+ assertFalse(membershipKey.isValid());
+
+ try {
+ membershipKey.block(UNICAST_IPv4_1);
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ membershipKey.unblock(UNICAST_IPv4_1);
+ } catch (IllegalStateException expected) {
+ }
+
+ assertSame(dc, membershipKey.channel());
+ assertSame(GOOD_MULTICAST_IPv4, membershipKey.group());
+ assertSame(UNICAST_IPv4_1, membershipKey.sourceAddress());
+ assertSame(networkInterface1, membershipKey.networkInterface());
+ }
+
+ private static void configureChannelForReceiving(DatagramChannel receivingChannel)
+ throws Exception {
+
+ // NOTE: At the time of writing setSoTimeout() has no effect in the RI, making these tests hang
+ // if the channel is in blocking mode. configureBlocking(false) is used instead and rely on the
+ // network to the local host being instantaneous.
+ // receivingChannel.socket().setSoTimeout(200);
+ // receivingChannel.configureBlocking(true);
+ receivingChannel.configureBlocking(false);
+ }
+
+ private static boolean willWorkForMulticast(NetworkInterface iface) throws IOException {
+ return iface.isUp()
+ // Typically loopback interfaces do not support multicast, but they are ruled out
+ // explicitly here anyway.
+ && !iface.isLoopback() && iface.supportsMulticast()
+ && iface.getInetAddresses().hasMoreElements();
+ }
+
+ private static void sendMulticastMessage(InetAddress group, int port, String msg)
+ throws IOException {
+ sendMulticastMessage(group, port, msg, null /* networkInterface */);
+ }
+
+ private static void sendMulticastMessage(
+ InetAddress group, int port, String msg, NetworkInterface sendingInterface)
+ throws IOException {
+ // Any datagram socket can send to a group. It does not need to have joined the group.
+ DatagramChannel dc = DatagramChannel.open();
+ if (sendingInterface != null) {
+ // For some reason, if set, this must be set to a real (non-loopback) device for an IPv6
+ // group, but can be loopback for an IPv4 group.
+ dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, sendingInterface);
+ }
+ sendMessage(dc, msg, new InetSocketAddress(group, port));
+ dc.close();
+ }
+
+ private static void sendMessage(
+ DatagramChannel sendingChannel, String msg, InetSocketAddress targetAddress)
+ throws IOException {
+
+ ByteBuffer sendBuffer = ByteBuffer.wrap(msg.getBytes());
+ sendingChannel.send(sendBuffer, targetAddress);
+ }
+
+ private static InetAddress getLocalIpv4Address(NetworkInterface networkInterface) {
+ for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
+ if (interfaceAddress.getAddress() instanceof Inet4Address) {
+ return interfaceAddress.getAddress();
+ }
+ }
+ throw new AssertionFailedError("Unable to find local IPv4 address for " + networkInterface);
+ }
+
+ private static InetAddress getLocalIpv6Address(NetworkInterface networkInterface) {
+ for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
+ if (interfaceAddress.getAddress() instanceof Inet6Address) {
+ return interfaceAddress.getAddress();
+ }
+ }
+ throw new AssertionFailedError("Unable to find local IPv6 address for " + networkInterface);
+ }
+
+}
diff --git a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
index 5927583..cd4bb22 100644
--- a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
@@ -19,13 +19,17 @@ package libcore.java.nio.channels;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
+import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
+import java.nio.channels.UnresolvedAddressException;
import java.util.Enumeration;
import java.util.Set;
@@ -446,4 +450,120 @@ public class DatagramChannelTest extends junit.framework.TestCase {
dc.close();
}
+ public void test_bind_unresolvedAddress() throws IOException {
+ DatagramChannel dc = DatagramChannel.open();
+ try {
+ dc.bind(new InetSocketAddress("unresolvedname", 31415));
+ fail();
+ } catch (UnresolvedAddressException expected) {
+ }
+
+ assertNull(dc.getLocalAddress());
+ assertTrue(dc.isOpen());
+ assertFalse(dc.isConnected());
+
+ dc.close();
+ }
+
+ public void test_bind_noReuseAddress() throws Exception {
+ DatagramChannel dc1 = DatagramChannel.open();
+ dc1.setOption(StandardSocketOptions.SO_REUSEADDR, false);
+ DatagramChannel dc2 = DatagramChannel.open();
+ dc1.setOption(StandardSocketOptions.SO_REUSEADDR, false);
+
+ dc1.bind(null);
+
+ try {
+ dc2.bind(dc1.getLocalAddress());
+ fail();
+ } catch (IOException expected) {}
+
+ dc1.close();
+ dc2.close();
+ }
+
+ public void test_bind_withReuseAddress() throws Exception {
+ DatagramChannel dc1 = DatagramChannel.open();
+ dc1.setOption(StandardSocketOptions.SO_REUSEADDR, true);
+ DatagramChannel dc2 = DatagramChannel.open();
+ dc2.setOption(StandardSocketOptions.SO_REUSEADDR, true);
+
+ dc1.bind(null);
+ dc2.bind(dc1.getLocalAddress());
+
+ dc1.close();
+ dc2.close();
+ }
+
+ public void test_bind_any_IPv4() throws Exception {
+ test_bind_any(InetAddress.getByName("0.0.0.0"));
+ }
+
+ public void test_bind_any_IPv6() throws Exception {
+ test_bind_any(InetAddress.getByName("::"));
+ }
+
+ private void test_bind_any(InetAddress bindAddress) throws Exception {
+ DatagramChannel dc = DatagramChannel.open();
+ dc.bind(new InetSocketAddress(bindAddress, 0));
+
+ assertTrue(dc.isOpen());
+ assertFalse(dc.isConnected());
+
+ InetSocketAddress actualAddress = (InetSocketAddress) dc.getLocalAddress();
+ assertTrue(actualAddress.getAddress().isAnyLocalAddress());
+ assertTrue(actualAddress.getPort() > 0);
+
+ dc.close();
+ }
+
+ public void test_bind_loopback_IPv4() throws Exception {
+ test_bind(InetAddress.getByName("127.0.0.1"));
+ }
+
+ public void test_bind_loopback_IPv6() throws Exception {
+ test_bind(InetAddress.getByName("::1"));
+ }
+
+ public void test_bind_IPv4() throws Exception {
+ InetAddress bindAddress = getNonLoopbackNetworkInterfaceAddress(true /* ipv4 */);
+ test_bind(bindAddress);
+ }
+
+ public void test_bind_IPv6() throws Exception {
+ InetAddress bindAddress = getNonLoopbackNetworkInterfaceAddress(false /* ipv4 */);
+ test_bind(bindAddress);
+ }
+
+ private void test_bind(InetAddress bindAddress) throws IOException {
+ DatagramChannel dc = DatagramChannel.open();
+ dc.bind(new InetSocketAddress(bindAddress, 0));
+
+ InetSocketAddress actualAddress = (InetSocketAddress) dc.getLocalAddress();
+ assertEquals(bindAddress, actualAddress.getAddress());
+ assertTrue(actualAddress.getPort() > 0);
+
+ dc.close();
+ }
+
+ private static InetAddress getNonLoopbackNetworkInterfaceAddress(boolean ipv4)
+ throws SocketException {
+
+ Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (networkInterfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = networkInterfaces.nextElement();
+ if (networkInterface.isLoopback() || !networkInterface.isUp()) {
+ continue;
+ }
+ Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
+ while (inetAddresses.hasMoreElements()) {
+ InetAddress inetAddress = inetAddresses.nextElement();
+ if ( (ipv4 && inetAddress instanceof Inet4Address)
+ || (!ipv4 && inetAddress instanceof Inet6Address)) {
+ return inetAddress;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java
index ed39d46..e819d82 100644
--- a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java
@@ -26,6 +26,7 @@ import java.net.StandardSocketOptions;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
+import java.nio.channels.UnresolvedAddressException;
import java.util.Enumeration;
import java.util.Set;
@@ -63,6 +64,20 @@ public class ServerSocketChannelTest extends junit.framework.TestCase {
}
}
+ public void test_bind_unresolvedAddress() throws IOException {
+ ServerSocketChannel ssc = ServerSocketChannel.open();
+ try {
+ ssc.bind(new InetSocketAddress("unresolvedname", 31415));
+ fail();
+ } catch (UnresolvedAddressException expected) {
+ }
+
+ assertNull(ssc.getLocalAddress());
+ assertTrue(ssc.isOpen());
+
+ ssc.close();
+ }
+
public void test_bind_nullBindsToAll() throws Exception {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(null);
diff --git a/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java
index 118a168..bd23b3f 100644
--- a/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java
@@ -31,6 +31,7 @@ import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
+import java.nio.channels.UnresolvedAddressException;
import java.util.Set;
import tests.io.MockOs;
@@ -162,6 +163,21 @@ public class SocketChannelTest extends junit.framework.TestCase {
}
}
+ public void test_bind_unresolvedAddress() throws IOException {
+ SocketChannel sc = SocketChannel.open();
+ try {
+ sc.bind(new InetSocketAddress("unresolvedname", 31415));
+ fail();
+ } catch (UnresolvedAddressException expected) {
+ }
+
+ assertNull(sc.getLocalAddress());
+ assertTrue(sc.isOpen());
+ assertFalse(sc.isConnected());
+
+ sc.close();
+ }
+
/** Checks that the SocketChannel and associated Socket agree on the socket state. */
public void test_bind_socketStateSync() throws IOException {
SocketChannel sc = SocketChannel.open();