1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.net;
20 
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.hadoop.classification.InterfaceAudience;
24 import org.apache.hadoop.classification.InterfaceStability;
25 
26 import java.net.InetAddress;
27 import java.net.NetworkInterface;
28 import java.net.SocketException;
29 import java.net.UnknownHostException;
30 import java.util.Collections;
31 import java.util.Enumeration;
32 import java.util.LinkedHashSet;
33 import java.util.Vector;
34 
35 import javax.naming.NamingException;
36 import javax.naming.directory.Attributes;
37 import javax.naming.directory.DirContext;
38 import javax.naming.directory.InitialDirContext;
39 
40 /**
41  *
42  * A class that provides direct and reverse lookup functionalities, allowing
43  * the querying of specific network interfaces or nameservers.
44  *
45  *
46  */
47 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
48 @InterfaceStability.Unstable
49 public class DNS {
50 
51   private static final Log LOG = LogFactory.getLog(DNS.class);
52 
53   /**
54    * The cached hostname -initially null.
55    */
56 
57   private static final String cachedHostname = resolveLocalHostname();
58   private static final String cachedHostAddress = resolveLocalHostIPAddress();
59   private static final String LOCALHOST = "localhost";
60 
61   /**
62    * Returns the hostname associated with the specified IP address by the
63    * provided nameserver.
64    *
65    * Loopback addresses
66    * @param hostIp The address to reverse lookup
67    * @param ns The host name of a reachable DNS server
68    * @return The host name associated with the provided IP
69    * @throws NamingException If a NamingException is encountered
70    */
reverseDns(InetAddress hostIp, String ns)71   public static String reverseDns(InetAddress hostIp, String ns)
72     throws NamingException {
73     //
74     // Builds the reverse IP lookup form
75     // This is formed by reversing the IP numbers and appending in-addr.arpa
76     //
77     String[] parts = hostIp.getHostAddress().split("\\.");
78     String reverseIP = parts[3] + "." + parts[2] + "." + parts[1] + "."
79       + parts[0] + ".in-addr.arpa";
80 
81     DirContext ictx = new InitialDirContext();
82     Attributes attribute;
83     try {
84       attribute = ictx.getAttributes("dns://"               // Use "dns:///" if the default
85                          + ((ns == null) ? "" : ns) +
86                          // nameserver is to be used
87                          "/" + reverseIP, new String[] { "PTR" });
88     } finally {
89       ictx.close();
90     }
91 
92     String hostname = attribute.get("PTR").get().toString();
93     int hostnameLength = hostname.length();
94     if (hostname.charAt(hostnameLength - 1) == '.') {
95       hostname = hostname.substring(0, hostnameLength - 1);
96     }
97     return hostname;
98   }
99 
100   /**
101    * @return NetworkInterface for the given subinterface name (eg eth0:0)
102    *    or null if no interface with the given name can be found
103    */
getSubinterface(String strInterface)104   private static NetworkInterface getSubinterface(String strInterface)
105       throws SocketException {
106     Enumeration<NetworkInterface> nifs =
107       NetworkInterface.getNetworkInterfaces();
108 
109     while (nifs.hasMoreElements()) {
110       Enumeration<NetworkInterface> subNifs =
111         nifs.nextElement().getSubInterfaces();
112 
113       while (subNifs.hasMoreElements()) {
114         NetworkInterface nif = subNifs.nextElement();
115         if (nif.getName().equals(strInterface)) {
116           return nif;
117         }
118       }
119     }
120     return null;
121   }
122 
123   /**
124    * @param nif network interface to get addresses for
125    * @return set containing addresses for each subinterface of nif,
126    *    see below for the rationale for using an ordered set
127    */
getSubinterfaceInetAddrs( NetworkInterface nif)128   private static LinkedHashSet<InetAddress> getSubinterfaceInetAddrs(
129       NetworkInterface nif) {
130     LinkedHashSet<InetAddress> addrs = new LinkedHashSet<InetAddress>();
131     Enumeration<NetworkInterface> subNifs = nif.getSubInterfaces();
132     while (subNifs.hasMoreElements()) {
133       NetworkInterface subNif = subNifs.nextElement();
134       addrs.addAll(Collections.list(subNif.getInetAddresses()));
135     }
136     return addrs;
137   }
138 
139   /**
140    * Like {@link DNS#getIPs(String, boolean), but returns all
141    * IPs associated with the given interface and its subinterfaces.
142    */
getIPs(String strInterface)143   public static String[] getIPs(String strInterface)
144       throws UnknownHostException {
145     return getIPs(strInterface, true);
146   }
147 
148   /**
149    * Returns all the IPs associated with the provided interface, if any, in
150    * textual form.
151    *
152    * @param strInterface
153    *            The name of the network interface or sub-interface to query
154    *            (eg eth0 or eth0:0) or the string "default"
155    * @param returnSubinterfaces
156    *            Whether to return IPs associated with subinterfaces of
157    *            the given interface
158    * @return A string vector of all the IPs associated with the provided
159    *         interface. The local host IP is returned if the interface
160    *         name "default" is specified or there is an I/O error looking
161    *         for the given interface.
162    * @throws UnknownHostException
163    *             If the given interface is invalid
164    *
165    */
getIPs(String strInterface, boolean returnSubinterfaces)166   public static String[] getIPs(String strInterface,
167       boolean returnSubinterfaces) throws UnknownHostException {
168     if ("default".equals(strInterface)) {
169       return new String[] { cachedHostAddress };
170     }
171     NetworkInterface netIf;
172     try {
173       netIf = NetworkInterface.getByName(strInterface);
174       if (netIf == null) {
175         netIf = getSubinterface(strInterface);
176       }
177     } catch (SocketException e) {
178       LOG.warn("I/O error finding interface " + strInterface +
179           ": " + e.getMessage());
180       return new String[] { cachedHostAddress };
181     }
182     if (netIf == null) {
183       throw new UnknownHostException("No such interface " + strInterface);
184     }
185 
186     // NB: Using a LinkedHashSet to preserve the order for callers
187     // that depend on a particular element being 1st in the array.
188     // For example, getDefaultIP always returns the first element.
189     LinkedHashSet<InetAddress> allAddrs = new LinkedHashSet<InetAddress>();
190     allAddrs.addAll(Collections.list(netIf.getInetAddresses()));
191     if (!returnSubinterfaces) {
192       allAddrs.removeAll(getSubinterfaceInetAddrs(netIf));
193     }
194 
195     String ips[] = new String[allAddrs.size()];
196     int i = 0;
197     for (InetAddress addr : allAddrs) {
198       ips[i++] = addr.getHostAddress();
199     }
200     return ips;
201   }
202 
203 
204   /**
205    * Returns the first available IP address associated with the provided
206    * network interface or the local host IP if "default" is given.
207    *
208    * @param strInterface
209    *            The name of the network interface or subinterface to query
210    *             (e.g. eth0 or eth0:0) or the string "default"
211    * @return The IP address in text form, the local host IP is returned
212    *         if the interface name "default" is specified
213    * @throws UnknownHostException
214    *             If the given interface is invalid
215    */
getDefaultIP(String strInterface)216   public static String getDefaultIP(String strInterface)
217     throws UnknownHostException {
218     String[] ips = getIPs(strInterface);
219     return ips[0];
220   }
221 
222   /**
223    * Returns all the host names associated by the provided nameserver with the
224    * address bound to the specified network interface
225    *
226    * @param strInterface
227    *            The name of the network interface or subinterface to query
228    *            (e.g. eth0 or eth0:0)
229    * @param nameserver
230    *            The DNS host name
231    * @return A string vector of all host names associated with the IPs tied to
232    *         the specified interface
233    * @throws UnknownHostException if the given interface is invalid
234    */
getHosts(String strInterface, String nameserver)235   public static String[] getHosts(String strInterface, String nameserver)
236     throws UnknownHostException {
237     String[] ips = getIPs(strInterface);
238     Vector<String> hosts = new Vector<String>();
239     for (int ctr = 0; ctr < ips.length; ctr++) {
240       try {
241         hosts.add(reverseDns(InetAddress.getByName(ips[ctr]),
242                              nameserver));
243       } catch (UnknownHostException ignored) {
244       } catch (NamingException ignored) {
245       }
246     }
247     if (hosts.isEmpty()) {
248       LOG.warn("Unable to determine hostname for interface " + strInterface);
249       return new String[] { cachedHostname };
250     } else {
251       return hosts.toArray(new String[hosts.size()]);
252     }
253   }
254 
255 
256   /**
257    * Determine the local hostname; retrieving it from cache if it is known
258    * If we cannot determine our host name, return "localhost"
259    * @return the local hostname or "localhost"
260    */
resolveLocalHostname()261   private static String resolveLocalHostname() {
262     String localhost;
263     try {
264       localhost = InetAddress.getLocalHost().getCanonicalHostName();
265     } catch (UnknownHostException e) {
266       LOG.warn("Unable to determine local hostname "
267           + "-falling back to \"" + LOCALHOST + "\"", e);
268       localhost = LOCALHOST;
269     }
270     return localhost;
271   }
272 
273 
274   /**
275    * Get the IPAddress of the local host as a string.
276    * This will be a loop back value if the local host address cannot be
277    * determined.
278    * If the loopback address of "localhost" does not resolve, then the system's
279    * network is in such a state that nothing is going to work. A message is
280    * logged at the error level and a null pointer returned, a pointer
281    * which will trigger failures later on the application
282    * @return the IPAddress of the local host or null for a serious problem.
283    */
resolveLocalHostIPAddress()284   private static String resolveLocalHostIPAddress() {
285     String address;
286       try {
287         address = InetAddress.getLocalHost().getHostAddress();
288       } catch (UnknownHostException e) {
289         LOG.warn("Unable to determine address of the host"
290                 + "-falling back to \"" + LOCALHOST + "\" address", e);
291         try {
292           address = InetAddress.getByName(LOCALHOST).getHostAddress();
293         } catch (UnknownHostException noLocalHostAddressException) {
294           //at this point, deep trouble
295           LOG.error("Unable to determine local loopback address "
296                   + "of \"" + LOCALHOST + "\" " +
297                   "-this system's network configuration is unsupported", e);
298           address = null;
299         }
300       }
301     return address;
302   }
303 
304   /**
305    * Returns all the host names associated by the default nameserver with the
306    * address bound to the specified network interface
307    *
308    * @param strInterface
309    *            The name of the network interface to query (e.g. eth0)
310    * @return The list of host names associated with IPs bound to the network
311    *         interface
312    * @throws UnknownHostException
313    *             If one is encountered while querying the default interface
314    *
315    */
getHosts(String strInterface)316   public static String[] getHosts(String strInterface)
317     throws UnknownHostException {
318     return getHosts(strInterface, null);
319   }
320 
321   /**
322    * Returns the default (first) host name associated by the provided
323    * nameserver with the address bound to the specified network interface
324    *
325    * @param strInterface
326    *            The name of the network interface to query (e.g. eth0)
327    * @param nameserver
328    *            The DNS host name
329    * @return The default host names associated with IPs bound to the network
330    *         interface
331    * @throws UnknownHostException
332    *             If one is encountered while querying the default interface
333    */
getDefaultHost(String strInterface, String nameserver)334   public static String getDefaultHost(String strInterface, String nameserver)
335     throws UnknownHostException {
336     if ("default".equals(strInterface)) {
337       return cachedHostname;
338     }
339 
340     if ("default".equals(nameserver)) {
341       return getDefaultHost(strInterface);
342     }
343 
344     String[] hosts = getHosts(strInterface, nameserver);
345     return hosts[0];
346   }
347 
348   /**
349    * Returns the default (first) host name associated by the default
350    * nameserver with the address bound to the specified network interface
351    *
352    * @param strInterface
353    *            The name of the network interface to query (e.g. eth0).
354    *            Must not be null.
355    * @return The default host name associated with IPs bound to the network
356    *         interface
357    * @throws UnknownHostException
358    *             If one is encountered while querying the default interface
359    */
getDefaultHost(String strInterface)360   public static String getDefaultHost(String strInterface)
361     throws UnknownHostException {
362     return getDefaultHost(strInterface, null);
363   }
364 
365 }
366