001package org.apache.commons.jcs3.utils.net;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.net.InetAddress;
023import java.net.NetworkInterface;
024import java.net.SocketException;
025import java.net.UnknownHostException;
026import java.util.ArrayList;
027import java.util.Enumeration;
028/*
029 * or more contributor license agreements.  See the NOTICE file
030 * distributed with this work for additional information
031 * regarding copyright ownership.  The ASF licenses this file
032 * to you under the Apache License, Version 2.0 (the
033 * "License"); you may not use this file except in compliance
034 * with the License.  You may obtain a copy of the License at
035 *
036 *   http://www.apache.org/licenses/LICENSE-2.0
037 *
038 * Unless required by applicable law or agreed to in writing,
039 * software distributed under the License is distributed on an
040 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
041 * KIND, either express or implied.  See the License for the
042 * specific language governing permissions and limitations
043 * under the License.
044 */
045import java.util.List;
046
047import org.apache.commons.jcs3.log.Log;
048import org.apache.commons.jcs3.log.LogManager;
049
050/**
051 * Simple utility for getting the local host name.
052 */
053public class HostNameUtil
054{
055    /** The logger. */
056    private static final Log log = LogManager.getLog( HostNameUtil.class );
057
058    /**
059     * Gets the address for the local machine.
060     * <p>
061     * @return InetAddress.getLocalHost().getHostAddress()
062     * @throws UnknownHostException
063     */
064    public static String getLocalHostAddress() throws UnknownHostException
065    {
066        final String hostAddress = getLocalHostLANAddress().getHostAddress();
067        log.debug( "hostAddress = [{0}]", hostAddress );
068        return hostAddress;
069    }
070
071    /**
072     * Returns an <code>InetAddress</code> object encapsulating what is most likely the machine's
073     * LAN IP address.
074     * <p>
075     * This method is intended for use as a replacement of JDK method
076     * <code>InetAddress.getLocalHost</code>, because that method is ambiguous on Linux systems.
077     * Linux systems enumerate the loopback network interface the same way as regular LAN network
078     * interfaces, but the JDK <code>InetAddress.getLocalHost</code> method does not specify the
079     * algorithm used to select the address returned under such circumstances, and will often return
080     * the loopback address, which is not valid for network communication. Details <a
081     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4665037">here</a>.
082     * <p>
083     * This method will scan all IP addresses on all network interfaces on the host machine to
084     * determine the IP address most likely to be the machine's LAN address. If the machine has
085     * multiple IP addresses, this method will prefer a site-local IP address (e.g. 192.168.x.x or
086     * 10.10.x.x, usually IPv4) if the machine has one (and will return the first site-local address
087     * if the machine has more than one), but if the machine does not hold a site-local address,
088     * this method will return simply the first non-loopback address found (IPv4 or IPv6).</p>
089     * <p>
090     * If this method cannot find a non-loopback address using this selection algorithm, it will
091     * fall back to calling and returning the result of JDK method
092     * <code>InetAddress.getLocalHost</code>.
093     * <p>
094     * <a href="http://issues.apache.org/jira/browse/JCS-40">JIR ISSUE JCS-40</a>
095     * <p>
096     * @return InetAddress
097     * @throws UnknownHostException If the LAN address of the machine cannot be found.
098     * @since 3.1
099     */
100    public static InetAddress getLocalHostLANAddress()
101        throws UnknownHostException
102    {
103        return getLocalHostLANAddresses().stream().findFirst().orElse(null);
104    }
105
106    /**
107     * Returns all <code>InetAddress</code> objects encapsulating what are most likely the machine's
108     * LAN IP addresses.
109     * <p>
110     * This method will scan all IP addresses on all network interfaces on the host machine to
111     * determine the IP addresses most likely to be the machine's LAN addresses.
112     * <p>
113     * @return List<InetAddress>
114     * @throws UnknownHostException If the LAN address of the machine cannot be found.
115     */
116    public static List<InetAddress> getLocalHostLANAddresses()
117        throws UnknownHostException
118    {
119        List<InetAddress> addresses = new ArrayList<>();
120
121        try
122        {
123            InetAddress candidateAddress = null;
124            // Iterate all NICs (network interface cards)...
125            final Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
126            while ( ifaces.hasMoreElements() )
127            {
128                final NetworkInterface iface = ifaces.nextElement();
129
130                // Skip loopback interfaces
131                if (iface.isLoopback() || !iface.isUp())
132                {
133                    continue;
134                }
135
136                // Iterate all IP addresses assigned to each card...
137                for ( final Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); )
138                {
139                    final InetAddress inetAddr = inetAddrs.nextElement();
140                    if ( !inetAddr.isLoopbackAddress() )
141                    {
142                        if (inetAddr.isSiteLocalAddress())
143                        {
144                            // Found non-loopback site-local address.
145                            addresses.add(inetAddr);
146                        }
147                        if ( candidateAddress == null )
148                        {
149                            // Found non-loopback address, but not necessarily site-local.
150                            // Store it as a candidate to be returned if site-local address is not subsequently found...
151                            candidateAddress = inetAddr;
152                            // Note that we don't repeatedly assign non-loopback non-site-local addresses as candidates,
153                            // only the first. For subsequent iterations, candidate will be non-null.
154                        }
155                    }
156                }
157            }
158            if (candidateAddress != null && addresses.isEmpty())
159            {
160                // We did not find a site-local address, but we found some other non-loopback address.
161                // Server might have a non-site-local address assigned to its NIC (or it might be running
162                // IPv6 which deprecates the "site-local" concept).
163                addresses.add(candidateAddress);
164            }
165            // At this point, we did not find a non-loopback address.
166            // Fall back to returning whatever InetAddress.getLocalHost() returns...
167            if (addresses.isEmpty())
168            {
169                final InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
170                if ( jdkSuppliedAddress == null )
171                {
172                    throw new UnknownHostException( "The JDK InetAddress.getLocalHost() method unexpectedly returned null." );
173                }
174                addresses.add(jdkSuppliedAddress);
175            }
176        }
177        catch ( final SocketException e )
178        {
179            final UnknownHostException unknownHostException = new UnknownHostException( "Failed to determine LAN address: "
180                + e );
181            unknownHostException.initCause( e );
182            throw unknownHostException;
183        }
184
185        return addresses;
186    }
187
188    /**
189     * On systems with multiple network interfaces and mixed IPv6/IPv4 get a valid network
190     * interface for binding to multicast
191     *
192     * @return a network interface suitable for multicast
193     * @throws SocketException if a problem occurs while reading the network interfaces
194     */
195    public static NetworkInterface getMulticastNetworkInterface() throws SocketException
196    {
197        final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
198        while (networkInterfaces.hasMoreElements())
199        {
200            final NetworkInterface networkInterface = networkInterfaces.nextElement();
201            final Enumeration<InetAddress> addressesFromNetworkInterface = networkInterface.getInetAddresses();
202            while (addressesFromNetworkInterface.hasMoreElements())
203            {
204                final InetAddress inetAddress = addressesFromNetworkInterface.nextElement();
205                if (inetAddress.isSiteLocalAddress()
206                        && !inetAddress.isAnyLocalAddress()
207                        && !inetAddress.isLinkLocalAddress()
208                        && !inetAddress.isLoopbackAddress()
209                        && !inetAddress.isMulticastAddress())
210                {
211                    return networkInterface;
212                }
213            }
214        }
215
216        return null;
217    }
218}