| /* | |
| * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 4742177 8241786 | |
| * @summary Re-test IPv6 (and specifically MulticastSocket) with latest Linux & USAGI code | |
| * @requires os.family != "windows" | |
| * @library /test/lib | |
| * @run main/othervm SetOutgoingIf | |
| */ | |
| import java.io.IOException; | |
| import java.net.*; | |
| import java.util.*; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import jdk.test.lib.NetworkConfiguration; | |
| public class SetOutgoingIf implements AutoCloseable { | |
| private final MulticastSocket SOCKET; | |
| private final int PORT; | |
| private final Map<NetIf, MulticastSender> sendersMap = new ConcurrentHashMap<>(); | |
| private SetOutgoingIf() { | |
| try { | |
| SOCKET = new MulticastSocket(); | |
| PORT = SOCKET.getLocalPort(); | |
| } catch (IOException io) { | |
| throw new ExceptionInInitializerError(io); | |
| } | |
| } | |
| static boolean isMacOS() { | |
| return System.getProperty("os.name").contains("OS X"); | |
| } | |
| private static boolean hasIPv6() throws Exception { | |
| return NetworkConfiguration.probe() | |
| .ip6Addresses() | |
| .findAny() | |
| .isPresent(); | |
| } | |
| public static void main(String[] args) throws Exception { | |
| try (var test = new SetOutgoingIf()) { | |
| test.run(); | |
| } | |
| } | |
| @Override | |
| public void close() { | |
| try { | |
| SOCKET.close(); | |
| } finally { | |
| sendersMap.values().stream().forEach(MulticastSender::close); | |
| } | |
| } | |
| public void run() throws Exception { | |
| if (!hasIPv6()) { | |
| System.out.println("No IPv6 available. Bye."); | |
| return; | |
| } | |
| // We need 2 or more network interfaces to run the test | |
| // | |
| List<NetIf> netIfs = new ArrayList<NetIf>(); | |
| int index = 1; | |
| for (NetworkInterface nic : Collections.list(NetworkInterface.getNetworkInterfaces())) { | |
| // we should use only network interfaces with multicast support which are in "up" state | |
| if (!nic.isLoopback() && nic.supportsMulticast() && nic.isUp() && !isTestExcludedInterface(nic)) { | |
| NetIf netIf = NetIf.create(nic); | |
| // now determine what (if any) type of addresses are assigned to this interface | |
| for (InetAddress addr : Collections.list(nic.getInetAddresses())) { | |
| if (addr.isAnyLocalAddress()) | |
| continue; | |
| System.out.println(" addr " + addr); | |
| if (addr instanceof Inet4Address) { | |
| netIf.ipv4Address(true); | |
| } else if (addr instanceof Inet6Address) { | |
| netIf.ipv6Address(true); | |
| } | |
| } | |
| if (netIf.ipv4Address() || netIf.ipv6Address()) { | |
| netIf.index(index++); | |
| netIfs.add(netIf); | |
| debug("Using: " + nic); | |
| } | |
| } else { | |
| System.out.println("Ignore NetworkInterface nic == " + nic); | |
| } | |
| } | |
| Collections.reverse(netIfs); | |
| if (netIfs.size() <= 1) { | |
| System.out.println("Need 2 or more network interfaces to run. Bye."); | |
| return; | |
| } | |
| System.out.println("Using PORT: " + PORT); | |
| // We will send packets to one ipv4, and one ipv6 | |
| // multicast group using each network interface :- | |
| // 224.1.1.1 --| | |
| // ff02::1:1 --|--> using network interface #1 | |
| // 224.1.2.1 --| | |
| // ff02::1:2 --|--> using network interface #2 | |
| // and so on. | |
| // | |
| for (NetIf netIf : netIfs) { | |
| int NetIfIndex = netIf.index(); | |
| List<InetAddress> groups = new ArrayList<InetAddress>(); | |
| if (netIf.ipv4Address()) { | |
| InetAddress groupv4 = InetAddress.getByName("224.1." + NetIfIndex + ".1"); | |
| groups.add(groupv4); | |
| } | |
| if (netIf.ipv6Address()) { | |
| InetAddress groupv6 = InetAddress.getByName("ff02::1:" + NetIfIndex); | |
| groups.add(groupv6); | |
| } | |
| debug("Adding " + groups + " groups for " + netIf.nic().getName()); | |
| netIf.groups(groups); | |
| // use a separated thread to send to those 2 groups | |
| var multicastSender = new MulticastSender(netIf, groups, PORT); | |
| sendersMap.put(netIf, multicastSender); | |
| Thread sender = new Thread(multicastSender); | |
| sender.setDaemon(true); // we want sender to stop when main thread exits | |
| sender.start(); | |
| } | |
| // try to receive on each group, then check if the packet comes | |
| // from the expected network interface | |
| // | |
| byte[] buf = new byte[1024]; | |
| for (NetIf netIf : netIfs) { | |
| NetworkInterface nic = netIf.nic(); | |
| for (InetAddress group : netIf.groups()) { | |
| try (MulticastSocket mcastsock = new MulticastSocket(PORT)) { | |
| mcastsock.setSoTimeout(5000); // 5 second | |
| DatagramPacket packet = new DatagramPacket(buf, 0, buf.length); | |
| // the interface supports the IP multicast group | |
| debug("Joining " + group + " on " + nic.getName()); | |
| mcastsock.joinGroup(new InetSocketAddress(group, PORT), nic); | |
| try { | |
| mcastsock.receive(packet); | |
| debug("received packet on " + packet.getAddress()); | |
| } catch (Exception e) { | |
| // test failed if any exception | |
| throw new RuntimeException(e); | |
| } | |
| // now check which network interface this packet comes from | |
| NetworkInterface from = NetworkInterface.getByInetAddress(packet.getAddress()); | |
| NetworkInterface shouldbe = nic; | |
| if (from != null) { | |
| if (!from.equals(shouldbe)) { | |
| System.out.println("Packets on group " | |
| + group + " should come from " | |
| + shouldbe.getName() + ", but came from " | |
| + from.getName()); | |
| } | |
| } | |
| mcastsock.leaveGroup(new InetSocketAddress(group, PORT), nic); | |
| } | |
| } | |
| } | |
| } | |
| private static boolean isTestExcludedInterface(NetworkInterface nif) { | |
| return !NetworkConfiguration.isTestable(nif) | |
| || isMacOS() && nif.getName().startsWith("utun") | |
| || !NetworkConfiguration.hasNonLinkLocalAddress(nif); | |
| } | |
| private static boolean debug = true; | |
| static void debug(String message) { | |
| if (debug) | |
| System.out.println(message); | |
| } | |
| } | |
| class MulticastSender implements Runnable, AutoCloseable { | |
| private final NetIf netIf; | |
| private final List<InetAddress> groups; | |
| private final int port; | |
| private volatile boolean closed; | |
| private long count; | |
| public MulticastSender(NetIf netIf, | |
| List<InetAddress> groups, | |
| int port) { | |
| this.netIf = netIf; | |
| this.groups = groups; | |
| this.port = port; | |
| } | |
| @Override | |
| public void close() { | |
| closed = true; | |
| } | |
| public void run() { | |
| var nic = netIf.nic(); | |
| try (MulticastSocket mcastsock = new MulticastSocket()) { | |
| mcastsock.setNetworkInterface(nic); | |
| List<DatagramPacket> packets = new LinkedList<DatagramPacket>(); | |
| byte[] buf = "hello world".getBytes(); | |
| for (InetAddress group : groups) { | |
| packets.add(new DatagramPacket(buf, buf.length, new InetSocketAddress(group, port))); | |
| } | |
| while (!closed) { | |
| for (DatagramPacket packet : packets) { | |
| mcastsock.send(packet); | |
| count++; | |
| } | |
| System.out.printf("Sent %d packets from %s\n", count, nic.getName()); | |
| Thread.sleep(1000); // sleep 1 second | |
| } | |
| } catch (Exception e) { | |
| if (!closed) { | |
| System.err.println("Unexpected exception for MulticastSender(" | |
| + nic.getName() + "): " + e); | |
| e.printStackTrace(); | |
| throw new RuntimeException(e); | |
| } | |
| } finally { | |
| System.out.printf("Sent %d packets from %s\n", count, nic.getName()); | |
| } | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| class NetIf { | |
| private boolean ipv4Address; //false | |
| private boolean ipv6Address; //false | |
| private int index; | |
| List<InetAddress> groups = Collections.EMPTY_LIST; | |
| private final NetworkInterface nic; | |
| private NetIf(NetworkInterface nic) { | |
| this.nic = nic; | |
| } | |
| static NetIf create(NetworkInterface nic) { | |
| return new NetIf(nic); | |
| } | |
| NetworkInterface nic() { | |
| return nic; | |
| } | |
| boolean ipv4Address() { | |
| return ipv4Address; | |
| } | |
| void ipv4Address(boolean ipv4Address) { | |
| this.ipv4Address = ipv4Address; | |
| } | |
| boolean ipv6Address() { | |
| return ipv6Address; | |
| } | |
| void ipv6Address(boolean ipv6Address) { | |
| this.ipv6Address = ipv6Address; | |
| } | |
| int index() { | |
| return index; | |
| } | |
| void index(int index) { | |
| this.index = index; | |
| } | |
| List<InetAddress> groups() { | |
| return groups; | |
| } | |
| void groups(List<InetAddress> groups) { | |
| this.groups = groups; | |
| } | |
| } |