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"));
+ }
}