/* * 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 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 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 { check_joinAnySource(GOOD_MULTICAST_IPv4, BAD_MULTICAST_IPv4, ipv4NetworkInterface); } public void test_joinAnySource_IPv6() throws Exception { check_joinAnySource(GOOD_MULTICAST_IPv6, BAD_MULTICAST_IPv6, ipv6NetworkInterface); } private void check_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, networkInterface); // 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, networkInterface); 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 realInterfaces = new ArrayList(); Enumeration 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 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 { check_joinAnySource_multiple_joins(GOOD_MULTICAST_IPv4, ipv4NetworkInterface); } public void test_joinAnySource_multiple_joins_IPv6() throws Exception { check_joinAnySource_multiple_joins(GOOD_MULTICAST_IPv6, ipv6NetworkInterface); } private void check_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 { check_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv4, ipv4NetworkInterface); } public void test_multicastLoopOption_IPv6() throws Exception { check_joinAnySource_multicastLoopOption(GOOD_MULTICAST_IPv6, ipv6NetworkInterface); } private void check_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 */); dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface); 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 { checkMembershipKeyAccessors(GOOD_MULTICAST_IPv4, ipv4NetworkInterface); } public void testMembershipKeyAccessors_IPv6() throws Exception { checkMembershipKeyAccessors(GOOD_MULTICAST_IPv6, ipv6NetworkInterface); } private void checkMembershipKeyAccessors(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 { check_dropAnySource_twice(GOOD_MULTICAST_IPv4, ipv4NetworkInterface); } public void test_dropAnySource_twice_IPv6() throws Exception { check_dropAnySource_twice(GOOD_MULTICAST_IPv6, ipv6NetworkInterface); } private void check_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); check_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); check_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); check_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); check_block_filtersAsExpected( ipv6LocalAddress /* senderBindAddress */, WILDCARD_IPv6 /* receiverBindAddress */, GOOD_MULTICAST_IPv6 /* groupAddress */, ipv6NetworkInterface); } private void check_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); check_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); check_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_ipv6() throws Exception { if (!supportsMulticast) { return; } InetAddress ipv6LocalAddress = getLocalIpv6Address(ipv6NetworkInterface); check_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); check_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); check_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 * @param networkInterface The network interface on which to join the multicast group */ private void check_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 { check_dropSourceSpecific_twice( GOOD_MULTICAST_IPv4 /* groupAddress */, UNICAST_IPv4_1 /* sourceAddress */, ipv4NetworkInterface); } public void test_dropSourceSpecific_twice_IPv6() throws Exception { check_dropSourceSpecific_twice( GOOD_MULTICAST_IPv6 /* groupAddress */, UNICAST_IPv6_1 /* sourceAddress */, ipv6NetworkInterface); } private void check_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 { check_dropSourceSpecific_sourceKeysAreIndependent( GOOD_MULTICAST_IPv4 /* groupAddress */, UNICAST_IPv4_1 /* sourceAddress1 */, UNICAST_IPv4_2 /* sourceAddress2 */, ipv4NetworkInterface); } public void test_dropSourceSpecific_sourceKeysAreIndependent_IPv6() throws Exception { check_dropSourceSpecific_sourceKeysAreIndependent( GOOD_MULTICAST_IPv6 /* groupAddress */, UNICAST_IPv6_1 /* sourceAddress1 */, UNICAST_IPv6_2 /* sourceAddress2 */, ipv6NetworkInterface); } private void check_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, 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); } }