/* * 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 org.apache.harmony.luni.net.NetUtil; import org.apache.harmony.luni.net.PlainDatagramSocketImpl; import org.apache.harmony.luni.util.Msg; /** * This class implements a multicast socket for sending and receiving IP * multicast datagram packets. * * @see DatagramSocket */ public class MulticastSocket extends DatagramSocket { final static int SO_REUSEPORT = 512; private InetAddress interfaceSet; /** * Constructs a multicast socket, bound to any available port on the * localhost. * * @throws IOException * if an error occurs creating or binding the socket. */ public MulticastSocket() throws IOException { super(); setReuseAddress(true); } /** * Constructs a multicast socket, bound to the specified port on the * localhost. * * @param aPort * the port to bind on the localhost. * @throws IOException * if an error occurs creating or binding the socket. */ public MulticastSocket(int aPort) throws IOException { super(aPort); setReuseAddress(true); } /** * Gets the network address used by this socket. This is useful on * multihomed machines. * * @return the address of the network interface through which the datagram * packets are sent or received. * @throws SocketException * if an error occurs while getting the interface address. */ public InetAddress getInterface() throws SocketException { checkClosedAndBind(false); if (interfaceSet == null) { 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; } return interfaceSet; } /** * Gets the network interface used by this socket. This is useful on * multihomed machines. * * @return the network interface used by this socket or {@code null} if no * interface is set. * @throws SocketException * if an error occurs while getting the interface. * @since 1.4 */ public NetworkInterface getNetworkInterface() throws SocketException { checkClosedAndBind(false); // check if it is set at the IPV6 level. If so then use that. Otherwise // do it at the IPV4 level Integer theIndex = Integer.valueOf(0); try { theIndex = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2); } catch (SocketException e) { // we may get an exception if IPV6 is not enabled. } if (theIndex.intValue() != 0) { Enumeration<NetworkInterface> theInterfaces = NetworkInterface .getNetworkInterfaces(); while (theInterfaces.hasMoreElements()) { NetworkInterface nextInterface = theInterfaces.nextElement(); if (nextInterface.getIndex() == theIndex.intValue()) { return nextInterface; } } } // ok it was not set at the IPV6 level so try at the IPV4 level InetAddress theAddress = (InetAddress) impl .getOption(SocketOptions.IP_MULTICAST_IF); if (theAddress != null) { if (!theAddress.isAnyLocalAddress()) { return NetworkInterface.getByInetAddress(theAddress); } // not set as we got the any address so return a dummy network // interface with only the any address. We do this to be // compatible InetAddress theAddresses[] = new InetAddress[1]; if (!Socket.preferIPv4Stack() && NetUtil.preferIPv6Addresses()) { theAddresses[0] = Inet6Address.ANY; } else { theAddresses[0] = Inet4Address.ANY; } return new NetworkInterface(null, null, theAddresses, NetworkInterface.UNSET_INTERFACE_INDEX); } // ok not set at all so return null return null; } /** * Gets the time-to-live (TTL) for multicast packets sent on this socket. * * @return the default value for the time-to-life field. * @throws IOException * if an error occurs reading the default value. */ public int getTimeToLive() throws IOException { checkClosedAndBind(false); return impl.getTimeToLive(); } /** * Gets the time-to-live (TTL) for multicast packets sent on this socket. * * @return the default value for the time-to-life field. * @throws IOException * if an error occurs reading the default value. * @deprecated Replaced by {@link #getTimeToLive} * @see #getTimeToLive() */ @Deprecated public byte getTTL() throws IOException { checkClosedAndBind(false); 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 while joining a group. */ public void joinGroup(InetAddress groupAddr) throws IOException { checkClosedAndBind(false); if (!groupAddr.isMulticastAddress()) { throw new IOException(Msg.getString("K0039")); //$NON-NLS-1$ } SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkMulticast(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 SecurityException * if the caller is not authorized to join the group. * @throws IllegalArgumentException * if no multicast group is specified. * @since 1.4 */ public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException { checkClosedAndBind(false); if (null == groupAddress) { throw new IllegalArgumentException(Msg.getString("K0318")); //$NON-NLS-1$ } if ((netInterface != null) && (netInterface.getFirstAddress() == null)) { // this is ok if we could set it at the throw new SocketException(Msg.getString("K0335")); //$NON-NLS-1$ } if (!(groupAddress instanceof InetSocketAddress)) { throw new IllegalArgumentException(Msg.getString( "K0316", groupAddress.getClass())); //$NON-NLS-1$ } InetAddress groupAddr = ((InetSocketAddress) groupAddress).getAddress(); if (groupAddr == null) { throw new SocketException(Msg.getString("K0331")); //$NON-NLS-1$ } if (!groupAddr.isMulticastAddress()) { throw new IOException(Msg.getString("K0039")); //$NON-NLS-1$ } SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkMulticast(groupAddr); } 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. * @throws SecurityException * if the caller is not authorized to leave the group. */ public void leaveGroup(InetAddress groupAddr) throws IOException { checkClosedAndBind(false); if (!groupAddr.isMulticastAddress()) { throw new IOException(Msg.getString("K003a")); //$NON-NLS-1$ } SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkMulticast(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 SecurityException * if the caller is not authorized to leave the group. * @throws IllegalArgumentException * if {@code groupAddress} is {@code null}. * @since 1.4 */ public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException { checkClosedAndBind(false); if (null == groupAddress) { throw new IllegalArgumentException(Msg.getString("K0318")); //$NON-NLS-1$ } if ((netInterface != null) && (netInterface.getFirstAddress() == null)) { // this is ok if we could set it at the throw new SocketException(Msg.getString("K0335")); //$NON-NLS-1$ } if (!(groupAddress instanceof InetSocketAddress)) { throw new IllegalArgumentException(Msg.getString( "K0316", groupAddress.getClass())); //$NON-NLS-1$ } InetAddress groupAddr = ((InetSocketAddress) groupAddress).getAddress(); if (groupAddr == null) { throw new SocketException(Msg.getString("K0331")); //$NON-NLS-1$ } if (!groupAddr.isMulticastAddress()) { throw new IOException(Msg.getString("K003a")); //$NON-NLS-1$ } SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkMulticast(groupAddr); } impl.leaveGroup(groupAddress, netInterface); } /** * Send the packet on this socket. The packet must satisfy the security * policy before it may be sent. * * @param pack * the {@code DatagramPacket} to send * @param ttl * the TTL setting for this transmission, overriding the socket * default * @throws IOException * if an error occurs while sending data or setting options. * @deprecated use {@link #setTimeToLive}. */ @Deprecated public void send(DatagramPacket pack, byte ttl) throws IOException { checkClosedAndBind(false); InetAddress packAddr = pack.getAddress(); SecurityManager security = System.getSecurityManager(); if (security != null) { if (packAddr.isMulticastAddress()) { security.checkMulticast(packAddr, ttl); } else { security.checkConnect(packAddr.getHostName(), pack.getPort()); } } int currTTL = getTimeToLive(); if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) { try { setTimeToLive(ttl & 0xff); impl.send(pack); } finally { setTimeToLive(currTTL); } } else { impl.send(pack); } } /** * Sets the interface address used by this socket. This allows to send * multicast packets on a different interface than the default interface of * the local system. This is useful on multihomed machines. * * @param addr * the multicast interface network address to set. * @throws SocketException * if an error occurs while setting the network interface * address option. */ public void setInterface(InetAddress addr) throws SocketException { checkClosedAndBind(false); if (addr == null) { throw new NullPointerException(); } if (addr.isAnyLocalAddress()) { impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY); } else if (addr instanceof Inet4Address) { impl.setOption(SocketOptions.IP_MULTICAST_IF, addr); // keep the address used to do the set as we must return the same // value and for IPv6 we may not be able to get it back uniquely interfaceSet = addr; } /* * now we should also make sure this works for IPV6 get the network * interface for the address and set the interface using its index * however if IPV6 is not enabled then we may get an exception. if IPV6 * is not enabled */ NetworkInterface theInterface = NetworkInterface.getByInetAddress(addr); if ((theInterface != null) && (theInterface.getIndex() != 0)) { try { impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer .valueOf(theInterface.getIndex())); } catch (SocketException e) { // Ignored } } else if (addr.isAnyLocalAddress()) { try { impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer .valueOf(0)); } catch (SocketException e) { // Ignored } } else if (addr instanceof Inet6Address) { throw new SocketException(Msg.getString("K0338")); //$NON-NLS-1$ } } /** * Sets the network interface used by this socket. This is useful for * multihomed machines. * * @param netInterface * the multicast network interface to set. * @throws SocketException * if an error occurs while setting the network interface * option. * @since 1.4 */ public void setNetworkInterface(NetworkInterface netInterface) throws SocketException { checkClosedAndBind(false); if (netInterface == null) { // throw a socket exception indicating that we do not support this throw new SocketException(Msg.getString("K0334")); //$NON-NLS-1$ } InetAddress firstAddress = netInterface.getFirstAddress(); if (firstAddress == null) { // this is ok if we could set it at the throw new SocketException(Msg.getString("K0335")); //$NON-NLS-1$ } if (netInterface.getIndex() == NetworkInterface.UNSET_INTERFACE_INDEX) { // set the address using IP_MULTICAST_IF to make sure this // works for both IPV4 and IPV6 impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY); try { // we have the index so now we pass set the interface // using IP_MULTICAST_IF2. This is what is used to set // the interface on systems which support IPV6 impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer .valueOf(NetworkInterface.NO_INTERFACE_INDEX)); } catch (SocketException e) { // for now just do this, -- could be narrowed? } } /* * Now try to set using IPV4 way. However, if interface passed in has no * IP addresses associated with it then we cannot do it. first we have * to make sure there is an IPV4 address that we can use to call set * interface otherwise we will not set it */ Enumeration<InetAddress> theAddresses = netInterface.getInetAddresses(); boolean found = false; firstAddress = null; while ((theAddresses.hasMoreElements()) && (found != true)) { InetAddress theAddress = theAddresses.nextElement(); if (theAddress instanceof Inet4Address) { firstAddress = theAddress; found = true; } } if (netInterface.getIndex() == NetworkInterface.NO_INTERFACE_INDEX) { // the system does not support IPV6 and does not provide // indexes for the network interfaces. Just pass in the // first address for the network interface if (firstAddress != null) { impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress); } else { /* * we should never get here as there should not be any network * interfaces which have no IPV4 address and which does not have * the network interface index not set correctly */ throw new SocketException(Msg.getString("K0335")); //$NON-NLS-1$ } } else { // set the address using IP_MULTICAST_IF to make sure this // works for both IPV4 and IPV6 if (firstAddress != null) { impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress); } try { // we have the index so now we pass set the interface // using IP_MULTICAST_IF2. This is what is used to set // the interface on systems which support IPV6 impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer .valueOf(netInterface.getIndex())); } catch (SocketException e) { // for now just do this -- could be narrowed? } } interfaceSet = null; } /** * Sets the time-to-live (TTL) for multicast packets sent on this socket. * Valid TTL values are between 0 and 255 inclusive. * * @param ttl * the default time-to-live field value for packets sent on this * socket. {@code 0 <= ttl <= 255}. * @throws IOException * if an error occurs while setting the TTL option value. */ public void setTimeToLive(int ttl) throws IOException { checkClosedAndBind(false); if (ttl < 0 || ttl > 255) { throw new IllegalArgumentException(Msg.getString("K003c")); //$NON-NLS-1$ } 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. * * @param ttl * the default time-to-live field value for packets sent on this * socket: {@code 0 <= ttl <= 255}. * @throws IOException * if an error occurs while setting the TTL option value. * @deprecated Replaced by {@link #setTimeToLive} * @see #setTimeToLive(int) */ @Deprecated public void setTTL(byte ttl) throws IOException { checkClosedAndBind(false); 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; } } /** * Constructs a {@code MulticastSocket} bound to the host/port specified by * the {@code SocketAddress}, or an unbound {@code DatagramSocket} if the * {@code SocketAddress} is {@code null}. * * @param localAddr * the local machine address and port to bind to. * @throws IllegalArgumentException * if the {@code SocketAddress} is not supported. * @throws IOException * if an error occurs creating or binding the socket. * @since 1.4 */ public MulticastSocket(SocketAddress localAddr) throws IOException { super(localAddr); setReuseAddress(true); } /** * Gets the state of the {@code SocketOptions.IP_MULTICAST_LOOP}. * * @return {@code true} if the IP multicast loop is enabled, {@code false} * otherwise. * @throws SocketException * if the socket is closed or the option is invalid. * @since 1.4 */ public boolean getLoopbackMode() throws SocketException { checkClosedAndBind(false); return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)) .booleanValue(); } /** * Sets the {@code SocketOptions.IP_MULTICAST_LOOP}. * * @param loop * the value for the socket option socket {@code * SocketOptions.IP_MULTICAST_LOOP}. * @throws SocketException * if the socket is closed or the option is invalid. * @since 1.4 */ public void setLoopbackMode(boolean loop) throws SocketException { checkClosedAndBind(false); impl.setOption(SocketOptions.IP_MULTICAST_LOOP, loop ? Boolean.FALSE : Boolean.TRUE); } }