blob: 69110e095ae67d5bd5d631a7f2c6820033e9c4a6 [file] [log] [blame]
/*
* Copyright (C) 2016 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.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.SocketTimeoutException;
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;
import libcore.io.IoBridge;
import static android.system.OsConstants.POLLIN;
/**
* Tests associated with multicast behavior of DatagramChannel.
* These tests require IPv6 multicasting enabled network.
*/
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 ipv4NetworkInterface;
private NetworkInterface ipv6NetworkInterface;
private NetworkInterface loopbackInterface;
private boolean supportsMulticast;
@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();
// Determine if the device is marked to support multicast or not. If this propery is not
// set we assume the device has an interface capable of supporting multicast.
supportsMulticast = Boolean.parseBoolean(
System.getProperty("android.cts.device.multicast", "true"));
if (!supportsMulticast) {
return;
}
while (interfaces.hasMoreElements()
&& (ipv4NetworkInterface == null || ipv6NetworkInterface == null)) {
NetworkInterface nextInterface = interfaces.nextElement();
if (willWorkForMulticast(nextInterface)) {
Enumeration<InetAddress> addresses = nextInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
final InetAddress nextAddress = addresses.nextElement();
if (nextAddress instanceof Inet6Address && ipv6NetworkInterface == null) {
ipv6NetworkInterface = nextInterface;
} else if (nextAddress instanceof Inet4Address
&& ipv4NetworkInterface == null) {
ipv4NetworkInterface = nextInterface;
}
}
}
}
if (ipv4NetworkInterface == null) {
fail("Test environment must have at least one network interface capable of IPv4"
+ " multicast");
}
if (ipv6NetworkInterface == null) {
fail("Test environment must have at least one network interface capable of IPv6"
+ " multicast");
}
}
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
dc.close();
try {
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
fail();
} catch (ClosedChannelException expected) {
}
}
public void test_joinAnySource_nullGroupAddress() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(null, ipv4NetworkInterface);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_joinAnySource_nullNetworkInterface() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv4, null);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_joinAnySource_nonMulticastGroupAddress_IPv4() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(UNICAST_IPv4_1, ipv4NetworkInterface);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinAnySource_nonMulticastGroupAddress_IPv6() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(UNICAST_IPv6_1, ipv6NetworkInterface);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinAnySource_IPv4() throws Exception {
test_joinAnySource(GOOD_MULTICAST_IPv4, BAD_MULTICAST_IPv4, ipv4NetworkInterface);
}
public void test_joinAnySource_IPv6() throws Exception {
test_joinAnySource(GOOD_MULTICAST_IPv6, BAD_MULTICAST_IPv6, ipv6NetworkInterface);
}
private void test_joinAnySource(InetAddress group, InetAddress group2,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
// Set up a receiver join the group on ipv4NetworkInterface
DatagramChannel receiverChannel = createReceiverChannel();
InetSocketAddress localAddress = (InetSocketAddress) receiverChannel.getLocalAddress();
receiverChannel.join(group, networkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
for (byte i = 1; i <= 25; i++) {
InetAddress groupAddress = Inet4Address.getByName("239.255.0." + i);
try {
dc.join(groupAddress, ipv4NetworkInterface);
} 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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey key = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = DatagramChannel.open();
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
assertNull(dc.getLocalAddress());
dc.close();
}
public void test_joinAnySource_networkInterfaces() throws Exception {
if (!supportsMulticast) {
return;
}
// 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 = ipv4NetworkInterface;
} else {
// if this interface only seems to support IPV6 addresses
group = GOOD_MULTICAST_IPv6;
sendingInterface = ipv6NetworkInterface;
}
}
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = DatagramChannel.open();
MembershipKey membershipKey1 = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
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, ipv4NetworkInterface);
}
public void test_joinAnySource_multiple_joins_IPv6()
throws Exception {
test_joinAnySource_multiple_joins(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
}
private void test_joinAnySource_multiple_joins(InetAddress group,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey1 = dc.join(group, networkInterface);
MembershipKey membershipKey2 = dc.join(group, loopbackInterface);
assertFalse(membershipKey1.equals(membershipKey2));
MembershipKey membershipKey1_2 = dc.join(group, networkInterface);
assertEquals(membershipKey1, membershipKey1_2);
dc.close();
}
public void test_joinAnySource_multicastLoopOption_IPv4() throws Exception {
test_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
}
public void test_multicastLoopOption_IPv6() throws Exception {
test_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
}
private void test_joinAnySource_multicastLoopOption(InetAddress group,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
final String message = "Hello, world!";
DatagramChannel dc = createReceiverChannel();
dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, true /* enable loop */);
configureChannelForReceiving(dc);
dc.join(group, networkInterface);
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, ipv4NetworkInterface);
}
public void testMembershipKeyAccessors_IPv6() throws Exception {
testMembershipKeyAccessors(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
}
private void testMembershipKeyAccessors(InetAddress group,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey key = dc.join(group, networkInterface);
assertSame(dc, key.channel());
assertSame(group, key.group());
assertTrue(key.isValid());
assertSame(networkInterface, key.networkInterface());
assertNull(key.sourceAddress());
dc.close();
}
public void test_dropAnySource_twice_IPv4() throws Exception {
test_dropAnySource_twice(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
}
public void test_dropAnySource_twice_IPv6() throws Exception {
test_dropAnySource_twice(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
}
private void test_dropAnySource_twice(InetAddress group,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(group, networkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
assertTrue(membershipKey.isValid());
dc.close();
assertFalse(membershipKey.isValid());
}
public void test_block_null() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
try {
membershipKey.block(null);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_block_mixedAddressTypes_IPv4() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
try {
membershipKey.block(UNICAST_IPv6_1);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_block_mixedAddressTypes_IPv6() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
try {
membershipKey.block(UNICAST_IPv4_1);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_block_cannotBlockWithSourceSpecificMembership() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, UNICAST_IPv4_1);
try {
membershipKey.block(UNICAST_IPv4_2);
fail();
} catch (IllegalStateException expected) {
}
dc.close();
}
public void test_block_multipleBlocksIgnored() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
membershipKey.block(UNICAST_IPv4_1);
MembershipKey membershipKey2 = membershipKey.block(UNICAST_IPv4_1);
assertSame(membershipKey2, membershipKey);
dc.close();
}
public void test_block_wildcardAddress() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
try {
membershipKey.block(WILDCARD_IPv4);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_unblock_multipleUnblocksFail() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv6, ipv6NetworkInterface);
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 {
if (!supportsMulticast) {
return;
}
InetAddress ipv4LocalAddress = getLocalIpv4Address(ipv4NetworkInterface);
test_block_filtersAsExpected(
ipv4LocalAddress /* senderBindAddress */,
GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
GOOD_MULTICAST_IPv4 /* groupAddress */,
ipv4NetworkInterface);
}
/** Checks that block() works when the receiver is bound to the multicast group address */
public void test_block_filtersAsExpected_groupBind_ipv6() throws Exception {
if (!supportsMulticast) {
return;
}
InetAddress ipv6LocalAddress = getLocalIpv6Address(ipv6NetworkInterface);
test_block_filtersAsExpected(
ipv6LocalAddress /* senderBindAddress */,
GOOD_MULTICAST_IPv6 /* receiverBindAddress */,
GOOD_MULTICAST_IPv6 /* groupAddress */,
ipv6NetworkInterface);
}
/** Checks that block() works when the receiver is bound to the "any" address */
public void test_block_filtersAsExpected_anyBind_ipv4() throws Exception {
if (!supportsMulticast) {
return;
}
InetAddress ipv4LocalAddress = getLocalIpv4Address(ipv4NetworkInterface);
test_block_filtersAsExpected(
ipv4LocalAddress /* senderBindAddress */,
WILDCARD_IPv4 /* receiverBindAddress */,
GOOD_MULTICAST_IPv4 /* groupAddress */,
ipv4NetworkInterface);
}
/** Checks that block() works when the receiver is bound to the "any" address */
public void test_block_filtersAsExpected_anyBind_ipv6() throws Exception {
if (!supportsMulticast) {
return;
}
InetAddress ipv6LocalAddress = getLocalIpv6Address(ipv6NetworkInterface);
test_block_filtersAsExpected(
ipv6LocalAddress /* senderBindAddress */,
WILDCARD_IPv6 /* receiverBindAddress */,
GOOD_MULTICAST_IPv6 /* groupAddress */,
ipv6NetworkInterface);
}
private void test_block_filtersAsExpected(
InetAddress senderBindAddress, InetAddress receiverBindAddress,
InetAddress groupAddress, NetworkInterface networkInterface)
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(), networkInterface);
ByteBuffer receiveBuffer = ByteBuffer.allocate(10);
// Send a message. It should be received.
String msg1 = "Hello1";
sendMessage(sendingChannel, msg1, groupSocketAddress);
IoBridge.poll(receivingChannel.socket().getFileDescriptor$(), POLLIN, 1000);
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);
try {
IoBridge.poll(receivingChannel.socket().getFileDescriptor$(), POLLIN, 1000);
fail();
} catch (SocketTimeoutException expected) { }
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);
IoBridge.poll(receivingChannel.socket().getFileDescriptor$(), POLLIN, 1000);
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(null, ipv4NetworkInterface, UNICAST_IPv4_1);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_afterClose() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
dc.close();
try {
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, UNICAST_IPv4_1);
fail();
} catch (ClosedChannelException expected) {
}
}
public void test_joinSourceSpecific_nullNetworkInterface() throws Exception {
if (!supportsMulticast) {
return;
}
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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(UNICAST_IPv4_1, ipv4NetworkInterface, UNICAST_IPv4_1);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_nonMulticastGroupAddress_IPv6() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(UNICAST_IPv6_1, ipv6NetworkInterface, UNICAST_IPv6_1);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_nullSourceAddress_IPv4() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, null);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_nullSourceAddress_IPv6() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv6, ipv6NetworkInterface, null);
fail();
} catch (NullPointerException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_mixedAddressTypes() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, UNICAST_IPv6_1);
fail();
} catch (IllegalArgumentException expected) {
}
try {
dc.join(GOOD_MULTICAST_IPv6, ipv6NetworkInterface, UNICAST_IPv4_1);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_nonUnicastSourceAddress_IPv4() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, BAD_MULTICAST_IPv4);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_nonUniicastSourceAddress_IPv6() throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
try {
dc.join(GOOD_MULTICAST_IPv6, ipv6NetworkInterface, BAD_MULTICAST_IPv6);
fail();
} catch (IllegalArgumentException expected) {
}
dc.close();
}
public void test_joinSourceSpecific_multipleSourceAddressLimit() throws Exception {
if (!supportsMulticast) {
return;
}
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, ipv4NetworkInterface, 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 {
if (!supportsMulticast) {
return;
}
InetAddress ipv4LocalAddress = getLocalIpv4Address(ipv4NetworkInterface);
test_joinSourceSpecific(
ipv4LocalAddress /* senderBindAddress */,
GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
GOOD_MULTICAST_IPv4 /* groupAddress */,
UNICAST_IPv4_1 /* badSenderAddress */,
ipv4NetworkInterface);
}
/**
* 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 {
if (!supportsMulticast) {
return;
}
InetAddress ipv4LocalAddress = getLocalIpv4Address(ipv4NetworkInterface);
test_joinSourceSpecific(
ipv4LocalAddress /* senderBindAddress */,
GOOD_MULTICAST_IPv4 /* receiverBindAddress */,
GOOD_MULTICAST_IPv4 /* groupAddress */,
UNICAST_IPv4_1 /* badSenderAddress */,
ipv6NetworkInterface);
}
/**
* 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 {
if (!supportsMulticast) {
return;
}
InetAddress ipv6LocalAddress = getLocalIpv6Address(ipv6NetworkInterface);
test_joinSourceSpecific(
ipv6LocalAddress /* senderBindAddress */,
GOOD_MULTICAST_IPv6 /* receiverBindAddress */,
GOOD_MULTICAST_IPv6 /* groupAddress */,
UNICAST_IPv6_1 /* badSenderAddress */,
ipv6NetworkInterface);
}
/** Checks that a source-specific join() works when the receiver is bound to the "any" address */
public void test_joinSourceSpecific_anyBind_ipv4() throws Exception {
if (!supportsMulticast) {
return;
}
InetAddress ipv4LocalAddress = getLocalIpv4Address(ipv4NetworkInterface);
test_joinSourceSpecific(
ipv4LocalAddress /* senderBindAddress */,
WILDCARD_IPv4 /* receiverBindAddress */,
GOOD_MULTICAST_IPv4 /* groupAddress */,
UNICAST_IPv4_1 /* badSenderAddress */,
ipv4NetworkInterface);
}
/** Checks that a source-specific join() works when the receiver is bound to the "any" address */
public void test_joinSourceSpecific_anyBind_ipv6() throws Exception {
if (!supportsMulticast) {
return;
}
InetAddress ipv6LocalAddress = getLocalIpv6Address(ipv6NetworkInterface);
test_joinSourceSpecific(
ipv6LocalAddress /* senderBindAddress */,
WILDCARD_IPv6 /* receiverBindAddress */,
GOOD_MULTICAST_IPv6 /* groupAddress */,
UNICAST_IPv6_1 /* badSenderAddress */,
ipv6NetworkInterface);
}
/**
* 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, NetworkInterface networkInterface)
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(), networkInterface, 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(), networkInterface, 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 */,
ipv4NetworkInterface);
}
public void test_dropSourceSpecific_twice_IPv6() throws Exception {
test_dropSourceSpecific_twice(
GOOD_MULTICAST_IPv6 /* groupAddress */, UNICAST_IPv6_1 /* sourceAddress */,
ipv6NetworkInterface);
}
private void test_dropSourceSpecific_twice(InetAddress groupAddress, InetAddress sourceAddress,
NetworkInterface networkInterface)
throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(groupAddress, networkInterface, 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 */,
ipv4NetworkInterface);
}
public void test_dropSourceSpecific_sourceKeysAreIndependent_IPv6() throws Exception {
test_dropSourceSpecific_sourceKeysAreIndependent(
GOOD_MULTICAST_IPv6 /* groupAddress */,
UNICAST_IPv6_1 /* sourceAddress1 */,
UNICAST_IPv6_2 /* sourceAddress2 */,
ipv6NetworkInterface);
}
private void test_dropSourceSpecific_sourceKeysAreIndependent(
InetAddress groupAddress, InetAddress sourceAddress1, InetAddress sourceAddress2,
NetworkInterface networkInterface) throws Exception {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey1 = dc.join(groupAddress, networkInterface, sourceAddress1);
MembershipKey membershipKey2 = dc.join(groupAddress, networkInterface, 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 {
if (!supportsMulticast) {
return;
}
DatagramChannel dc = createReceiverChannel();
MembershipKey membershipKey = dc.join(GOOD_MULTICAST_IPv4, ipv4NetworkInterface, UNICAST_IPv4_1);
membershipKey.drop();
assertFalse(membershipKey.isValid());
try {
membershipKey.block(UNICAST_IPv4_1);
fail();
} catch (IllegalStateException expected) {
}
try {
membershipKey.unblock(UNICAST_IPv4_1);
fail();
} catch (IllegalStateException expected) {
}
assertSame(dc, membershipKey.channel());
assertSame(GOOD_MULTICAST_IPv4, membershipKey.group());
assertSame(UNICAST_IPv4_1, membershipKey.sourceAddress());
assertSame(ipv4NetworkInterface, 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);
}
}