/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.net; import java.io.IOException; import java.util.Enumeration; import libcore.io.IoUtils; /** * This class implements a multicast socket for sending and receiving IP * multicast datagram packets. * * @see DatagramSocket */ public class MulticastSocket extends DatagramSocket { /** * Stores the address supplied to setInterface so we can return it from getInterface. The * translation to an interface index is lossy because an interface can have multiple addresses. */ private InetAddress setAddress; /** * Constructs a multicast socket, bound to any available port on the * local host. * * @throws IOException if an error occurs. */ public MulticastSocket() throws IOException { setReuseAddress(true); } /** * Constructs a multicast socket, bound to the specified {@code port} on the * local host. * * @throws IOException if an error occurs. */ public MulticastSocket(int port) throws IOException { super(port); setReuseAddress(true); } /** * Constructs a {@code MulticastSocket} bound to the address and port specified by * {@code localAddress}, or an unbound {@code MulticastSocket} if {@code localAddress == null}. * * @throws IllegalArgumentException if {@code localAddress} is not supported (because it's not * an {@code InetSocketAddress}, say). * @throws IOException if an error occurs. */ public MulticastSocket(SocketAddress localAddress) throws IOException { super(localAddress); setReuseAddress(true); } /** * Returns an address of the outgoing network interface used by this socket. To avoid * inherent unpredictability, new code should use {@link #getNetworkInterface} instead. * * @throws SocketException if an error occurs. */ public InetAddress getInterface() throws SocketException { checkOpen(); if (setAddress != null) { return setAddress; } InetAddress ipvXaddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF); if (ipvXaddress.isAnyLocalAddress()) { // the address was not set at the IPv4 level so check the IPv6 // level NetworkInterface theInterface = getNetworkInterface(); if (theInterface != null) { Enumeration<InetAddress> addresses = theInterface.getInetAddresses(); if (addresses != null) { while (addresses.hasMoreElements()) { InetAddress nextAddress = addresses.nextElement(); if (nextAddress instanceof Inet6Address) { return nextAddress; } } } } } return ipvXaddress; } /** * Returns the outgoing network interface used by this socket. * * @throws SocketException if an error occurs. */ public NetworkInterface getNetworkInterface() throws SocketException { checkOpen(); int index = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2); if (index != 0) { return NetworkInterface.getByIndex(index); } return NetworkInterface.forUnboundMulticastSocket(); } /** * Returns the time-to-live (TTL) for multicast packets sent on this socket. * * @throws IOException if an error occurs. */ public int getTimeToLive() throws IOException { checkOpen(); return impl.getTimeToLive(); } /** * Returns the time-to-live (TTL) for multicast packets sent on this socket. * * @throws IOException if an error occurs. * @deprecated Use {@link #getTimeToLive} instead. */ @Deprecated public byte getTTL() throws IOException { checkOpen(); return impl.getTTL(); } /** * Adds this socket to the specified multicast group. A socket must join a * group before data may be received. A socket may be a member of multiple * groups but may join any group only once. * * @param groupAddr * the multicast group to be joined. * @throws IOException if an error occurs. */ public void joinGroup(InetAddress groupAddr) throws IOException { checkJoinOrLeave(groupAddr); impl.join(groupAddr); } /** * Adds this socket to the specified multicast group. A socket must join a * group before data may be received. A socket may be a member of multiple * groups but may join any group only once. * * @param groupAddress * the multicast group to be joined. * @param netInterface * the network interface on which the datagram packets will be * received. * @throws IOException * if the specified address is not a multicast address. * @throws IllegalArgumentException * if no multicast group is specified. */ public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException { checkJoinOrLeave(groupAddress, netInterface); impl.joinGroup(groupAddress, netInterface); } /** * Removes this socket from the specified multicast group. * * @param groupAddr * the multicast group to be left. * @throws NullPointerException * if {@code groupAddr} is {@code null}. * @throws IOException * if the specified group address is not a multicast address. */ public void leaveGroup(InetAddress groupAddr) throws IOException { checkJoinOrLeave(groupAddr); impl.leave(groupAddr); } /** * Removes this socket from the specified multicast group. * * @param groupAddress * the multicast group to be left. * @param netInterface * the network interface on which the addresses should be * dropped. * @throws IOException * if the specified group address is not a multicast address. * @throws IllegalArgumentException * if {@code groupAddress} is {@code null}. */ public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException { checkJoinOrLeave(groupAddress, netInterface); impl.leaveGroup(groupAddress, netInterface); } private void checkJoinOrLeave(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException { checkOpen(); if (groupAddress == null) { throw new IllegalArgumentException("groupAddress == null"); } if (netInterface != null && !netInterface.getInetAddresses().hasMoreElements()) { throw new SocketException("No address associated with interface: " + netInterface); } if (!(groupAddress instanceof InetSocketAddress)) { throw new IllegalArgumentException("Group address not an InetSocketAddress: " + groupAddress.getClass()); } InetAddress groupAddr = ((InetSocketAddress) groupAddress).getAddress(); if (groupAddr == null) { throw new SocketException("Group address has no address: " + groupAddress); } if (!groupAddr.isMulticastAddress()) { throw new IOException("Not a multicast group: " + groupAddr); } } 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); } } /** * Sends the given {@code packet} on this socket, using the given {@code ttl}. This method is * deprecated because it modifies the TTL socket option for this socket twice on each call. * * @throws IOException if an error occurs. * @deprecated Use {@link #setTimeToLive} instead. */ @Deprecated public void send(DatagramPacket packet, byte ttl) throws IOException { checkOpen(); InetAddress packAddr = packet.getAddress(); int currTTL = getTimeToLive(); if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) { try { setTimeToLive(ttl & 0xff); impl.send(packet); } finally { setTimeToLive(currTTL); } } else { impl.send(packet); } } /** * Sets the outgoing network interface used by this socket. The interface used is the first * interface found to have the given {@code address}. To avoid inherent unpredictability, * new code should use {@link #getNetworkInterface} instead. * * @throws SocketException if an error occurs. */ public void setInterface(InetAddress address) throws SocketException { checkOpen(); if (address == null) { throw new NullPointerException("address == null"); } NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address); if (networkInterface == null) { throw new SocketException("Address not associated with an interface: " + address); } impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex()); this.setAddress = address; } /** * Sets the outgoing network interface used by this socket to the given * {@code networkInterface}. * * @throws SocketException if an error occurs. */ public void setNetworkInterface(NetworkInterface networkInterface) throws SocketException { checkOpen(); if (networkInterface == null) { throw new SocketException("networkInterface == null"); } impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex()); this.setAddress = null; } /** * Sets the time-to-live (TTL) for multicast packets sent on this socket. * Valid TTL values are between 0 and 255 inclusive. * * @throws IOException if an error occurs. */ public void setTimeToLive(int ttl) throws IOException { checkOpen(); if (ttl < 0 || ttl > 255) { throw new IllegalArgumentException("TimeToLive out of bounds: " + ttl); } impl.setTimeToLive(ttl); } /** * Sets the time-to-live (TTL) for multicast packets sent on this socket. * Valid TTL values are between 0 and 255 inclusive. * * @throws IOException if an error occurs. * @deprecated Use {@link #setTimeToLive} instead. */ @Deprecated public void setTTL(byte ttl) throws IOException { checkOpen(); impl.setTTL(ttl); } @Override synchronized void createSocket(int aPort, InetAddress addr) throws SocketException { impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl(); impl.create(); try { impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE); impl.bind(aPort, addr); isBound = true; } catch (SocketException e) { close(); throw e; } } /** * Returns true if multicast loopback is <i>disabled</i>. * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}. * * @throws SocketException if an error occurs. */ public boolean getLoopbackMode() throws SocketException { checkOpen(); return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue(); } /** * Disables multicast loopback if {@code disable == true}. * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}: true means disabled, false * means enabled. * * @throws SocketException if an error occurs. */ public void setLoopbackMode(boolean disable) throws SocketException { checkOpen(); impl.setOption(SocketOptions.IP_MULTICAST_LOOP, Boolean.valueOf(!disable)); } }