diff --git a/CHANGES.txt b/CHANGES.txt index 69ec0cbcaca..6b12a4dac53 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -124,6 +124,10 @@ Trunk (unreleased changes) HADOOP-7210. Chown command is not working from FSShell (Uma Maheswara Rao G via todd) + HADOOP-7215. RPC clients must use network interface corresponding to + the host in the client's kerberos principal key. (suresh) + +>>>>>>> .r1087843 Release 0.22.0 - Unreleased INCOMPATIBLE CHANGES diff --git a/src/java/org/apache/hadoop/ipc/Client.java b/src/java/org/apache/hadoop/ipc/Client.java index f671ab27ce6..d16170c7524 100644 --- a/src/java/org/apache/hadoop/ipc/Client.java +++ b/src/java/org/apache/hadoop/ipc/Client.java @@ -18,8 +18,11 @@ package org.apache.hadoop.ipc; +import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.Socket; import java.net.InetSocketAddress; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.net.ConnectException; @@ -412,6 +415,27 @@ private synchronized void setupConnection() throws IOException { try { this.socket = socketFactory.createSocket(); this.socket.setTcpNoDelay(tcpNoDelay); + + /* + * Bind the socket to the host specified in the principal name of the + * client, to ensure Server matching address of the client connection + * to host name in principal passed. + */ + if (UserGroupInformation.isSecurityEnabled()) { + KerberosInfo krbInfo = + remoteId.getProtocol().getAnnotation(KerberosInfo.class); + if (krbInfo != null && krbInfo.clientPrincipal() != null) { + String host = + SecurityUtil.getHostFromPrincipal(remoteId.getTicket().getUserName()); + + // If host name is a valid local address then bind socket to it + InetAddress localAddr = NetUtils.getLocalInetAddress(host); + if (localAddr != null) { + this.socket.bind(new InetSocketAddress(localAddr, 0)); + } + } + } + // connection time out is 20s NetUtils.connect(this.socket, remoteId.getAddress(), 20000); if (rpcTimeout > 0) { diff --git a/src/java/org/apache/hadoop/net/NetUtils.java b/src/java/org/apache/hadoop/net/NetUtils.java index bc81d5b79a8..4a9a7fe1a1b 100644 --- a/src/java/org/apache/hadoop/net/NetUtils.java +++ b/src/java/org/apache/hadoop/net/NetUtils.java @@ -22,8 +22,10 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.NetworkInterface; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketException; import java.net.URI; import java.net.UnknownHostException; import java.net.ConnectException; @@ -250,7 +252,7 @@ public static InetSocketAddress getConnectAddress(Server server) { * case, the timeout argument is ignored and the timeout set with * {@link Socket#setSoTimeout(int)} applies for reads.

* - * Any socket created using socket factories returned by {@link #NetUtils}, + * Any socket created using socket factories returned by {@link NetUtils}, * must use this interface instead of {@link Socket#getInputStream()}. * * @see #getInputStream(Socket, long) @@ -272,7 +274,7 @@ public static InputStream getInputStream(Socket socket) * case, the timeout argument is ignored and the timeout set with * {@link Socket#setSoTimeout(int)} applies for reads.

* - * Any socket created using socket factories returned by {@link #NetUtils}, + * Any socket created using socket factories returned by {@link NetUtils}, * must use this interface instead of {@link Socket#getInputStream()}. * * @see Socket#getChannel() @@ -301,7 +303,7 @@ public static InputStream getInputStream(Socket socket, long timeout) * case, the timeout argument is ignored and the write will wait until * data is available.

* - * Any socket created using socket factories returned by {@link #NetUtils}, + * Any socket created using socket factories returned by {@link NetUtils}, * must use this interface instead of {@link Socket#getOutputStream()}. * * @see #getOutputStream(Socket, long) @@ -323,7 +325,7 @@ public static OutputStream getOutputStream(Socket socket) * case, the timeout argument is ignored and the write will wait until * data is available.

* - * Any socket created using socket factories returned by {@link #NetUtils}, + * Any socket created using socket factories returned by {@link NetUtils}, * must use this interface instead of {@link Socket#getOutputStream()}. * * @see Socket#getChannel() @@ -426,6 +428,9 @@ public static List normalizeHostNames(Collection names) { return hostNames; } + private static final Pattern ipPattern = // Pattern for matching hostname to ip:port + Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:?\\d*"); + /** * Attempt to obtain the host name of a name specified by ip address. * Check that the node name is an ip addr and if so, attempt to determine @@ -434,8 +439,6 @@ public static List normalizeHostNames(Collection names) { * * @return Host name or null */ - private static final Pattern ipPattern = // Pattern for matching hostname to ip:port - Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:?\\d*"); public static String getHostNameOfIP(String ip) { // If name is not an ip addr, don't bother looking it up if(!ipPattern.matcher(ip).matches()) @@ -460,4 +463,27 @@ public static String getHostname() { try {return "" + InetAddress.getLocalHost();} catch(UnknownHostException uhe) {return "" + uhe;} } + + /** + * Checks if {@code host} is a local host name and return {@link InetAddress} + * corresponding to that address. + * + * @param host the specified host + * @return a valid local {@link InetAddress} or null + * @throws SocketException if an I/O error occurs + */ + public static InetAddress getLocalInetAddress(String host) + throws SocketException { + if (host == null) { + return null; + } + InetAddress addr = null; + try { + addr = InetAddress.getByName(host); + if (NetworkInterface.getByInetAddress(addr) == null) { + addr = null; // Not a local address + } + } catch (UnknownHostException ignore) { } + return addr; + } } diff --git a/src/java/org/apache/hadoop/security/SecurityUtil.java b/src/java/org/apache/hadoop/security/SecurityUtil.java index 4b2dff20351..85942f15c76 100644 --- a/src/java/org/apache/hadoop/security/SecurityUtil.java +++ b/src/java/org/apache/hadoop/security/SecurityUtil.java @@ -282,4 +282,13 @@ public static String buildDTServiceName(URI uri, int defPort) { sb.append(host).append(":").append(port); return sb.toString(); } + + /** + * Get the host name from the principal name of format /host@realm. + * @param principalName principal name of format as described above + * @return host name if the the string conforms to the above format, else null + */ + public static String getHostFromPrincipal(String principalName) { + return new KerberosName(principalName).getHostName(); + } } diff --git a/src/test/core/org/apache/hadoop/net/TestNetUtils.java b/src/test/core/org/apache/hadoop/net/TestNetUtils.java index e5b6a9c3b04..2aad71cb52e 100644 --- a/src/test/core/org/apache/hadoop/net/TestNetUtils.java +++ b/src/test/core/org/apache/hadoop/net/TestNetUtils.java @@ -24,6 +24,8 @@ import java.net.ConnectException; import java.net.SocketException; import java.net.InetSocketAddress; +import java.net.UnknownHostException; + import org.apache.hadoop.conf.Configuration; public class TestNetUtils { @@ -58,4 +60,16 @@ public void testAvoidLoopbackTcpSockets() throws Exception { assertTrue(se.getMessage().contains("Invalid argument")); } } + + /** + * Test for { + * @throws UnknownHostException @link NetUtils#getLocalInetAddress(String) + * @throws SocketException + */ + @Test + public void testGetLocalInetAddress() throws Exception { + assertNotNull(NetUtils.getLocalInetAddress("127.0.0.1")); + assertNull(NetUtils.getLocalInetAddress("invalid-address-for-test")); + assertNull(NetUtils.getLocalInetAddress(null)); + } } diff --git a/src/test/core/org/apache/hadoop/security/TestSecurityUtil.java b/src/test/core/org/apache/hadoop/security/TestSecurityUtil.java index b2bfaf383ab..e3358e9fd4f 100644 --- a/src/test/core/org/apache/hadoop/security/TestSecurityUtil.java +++ b/src/test/core/org/apache/hadoop/security/TestSecurityUtil.java @@ -113,4 +113,12 @@ public void testStartsWithIncorrectSettings() throws IOException { } assertTrue("Exception for empty keytabfile name was expected", gotException); } + + @Test + public void testGetHostFromPrincipal() { + assertEquals("host", + SecurityUtil.getHostFromPrincipal("service/host@realm")); + assertEquals(null, + SecurityUtil.getHostFromPrincipal("service@realm")); + } }