Skip to content

Commit 813e3c7

Browse files
committed
HADOOP-7104. Remove unnecessary DNS reverse lookups from RPC layer. Contributed by Kan Zhang
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1059235 13f79535-47bb-0310-9956-ffa450edef68
1 parent fdd4548 commit 813e3c7

File tree

6 files changed

+104
-65
lines changed

6 files changed

+104
-65
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ Trunk (unreleased changes)
4242
HADOOP-6995. Allow wildcards to be used in ProxyUsers configurations.
4343
(todd)
4444

45+
HADOOP-7104. Remove unnecessary DNS reverse lookups from RPC layer
46+
(Kan Zhang via todd)
47+
4548
OPTIMIZATIONS
4649

4750
BUG FIXES

src/java/org/apache/hadoop/ipc/Client.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ private static String getRemotePrincipal(Configuration conf,
12681268
+ protocol.getCanonicalName());
12691269
}
12701270
return SecurityUtil.getServerPrincipal(conf.get(serverKey), address
1271-
.getAddress().getCanonicalHostName());
1271+
.getAddress());
12721272
}
12731273
return null;
12741274
}

src/java/org/apache/hadoop/ipc/Server.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,8 @@ public class Connection {
851851
// Cache the remote host & port info so that even if the socket is
852852
// disconnected, we can say where it used to connect to.
853853
private String hostAddress;
854-
private String hostName;
855854
private int remotePort;
855+
private InetAddress addr;
856856

857857
ConnectionHeader header = new ConnectionHeader();
858858
Class<?> protocol;
@@ -889,12 +889,11 @@ public Connection(SelectionKey key, SocketChannel channel,
889889
this.unwrappedData = null;
890890
this.unwrappedDataLengthBuffer = ByteBuffer.allocate(4);
891891
this.socket = channel.socket();
892-
InetAddress addr = socket.getInetAddress();
892+
this.addr = socket.getInetAddress();
893893
if (addr == null) {
894894
this.hostAddress = "*Unknown*";
895895
} else {
896896
this.hostAddress = addr.getHostAddress();
897-
this.hostName = addr.getCanonicalHostName();
898897
}
899898
this.remotePort = socket.getPort();
900899
this.responseQueue = new LinkedList<Call>();
@@ -917,8 +916,8 @@ public String getHostAddress() {
917916
return hostAddress;
918917
}
919918

920-
public String getHostName() {
921-
return hostName;
919+
public InetAddress getHostInetAddress() {
920+
return addr;
922921
}
923922

924923
public void setLastContact(long lastContact) {
@@ -1326,7 +1325,7 @@ private boolean authorizeConnection() throws IOException {
13261325
&& (authMethod != AuthMethod.DIGEST)) {
13271326
ProxyUsers.authorize(user, this.getHostAddress(), conf);
13281327
}
1329-
authorize(user, header, getHostName());
1328+
authorize(user, header, getHostInetAddress());
13301329
if (LOG.isDebugEnabled()) {
13311330
LOG.debug("Successfully authorized " + header);
13321331
}
@@ -1666,12 +1665,12 @@ public abstract Writable call(Class<?> protocol,
16661665
*
16671666
* @param user client user
16681667
* @param connection incoming connection
1669-
* @param hostname fully-qualified domain name of incoming connection
1668+
* @param addr InetAddress of incoming connection
16701669
* @throws AuthorizationException when the client isn't authorized to talk the protocol
16711670
*/
16721671
public void authorize(UserGroupInformation user,
16731672
ConnectionHeader connection,
1674-
String hostname
1673+
InetAddress addr
16751674
) throws AuthorizationException {
16761675
if (authorize) {
16771676
Class<?> protocol = null;
@@ -1681,7 +1680,7 @@ public void authorize(UserGroupInformation user,
16811680
throw new AuthorizationException("Unknown protocol: " +
16821681
connection.getProtocol());
16831682
}
1684-
serviceAuthorizationManager.authorize(user, protocol, getConf(), hostname);
1683+
serviceAuthorizationManager.authorize(user, protocol, getConf(), addr);
16851684
}
16861685
}
16871686

src/java/org/apache/hadoop/security/SecurityUtil.java

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ public static void fetchServiceTicket(URL remoteHost) throws IOException {
135135
}
136136

137137
/**
138-
* Convert Kerberos principal name conf values to valid Kerberos principal
139-
* names. It replaces $host in the conf values with hostname, which should be
138+
* Convert Kerberos principal name pattern to valid Kerberos principal
139+
* names. It replaces hostname pattern with hostname, which should be
140140
* fully-qualified domain name. If hostname is null or "0.0.0.0", it uses
141141
* dynamically looked-up fqdn of the current host instead.
142142
*
@@ -149,24 +149,57 @@ public static void fetchServiceTicket(URL remoteHost) throws IOException {
149149
*/
150150
public static String getServerPrincipal(String principalConfig,
151151
String hostname) throws IOException {
152-
if (principalConfig == null)
153-
return null;
154-
String[] components = principalConfig.split("[/@]");
155-
if (components.length != 3) {
156-
throw new IOException(
157-
"Kerberos service principal name isn't configured properly "
158-
+ "(should have 3 parts): " + principalConfig);
159-
}
160-
161-
if (components[1].equals(HOSTNAME_PATTERN)) {
162-
String fqdn = hostname;
163-
if (fqdn == null || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
164-
fqdn = getLocalHostName();
165-
}
166-
return components[0] + "/" + fqdn + "@" + components[2];
152+
String[] components = getComponents(principalConfig);
153+
if (components == null || components.length != 3
154+
|| !components[1].equals(HOSTNAME_PATTERN)) {
155+
return principalConfig;
167156
} else {
157+
return replacePattern(components, hostname);
158+
}
159+
}
160+
161+
/**
162+
* Convert Kerberos principal name pattern to valid Kerberos principal names.
163+
* This method is similar to {@link #getServerPrincipal(String, String)},
164+
* except 1) the reverse DNS lookup from addr to hostname is done only when
165+
* necessary, 2) param addr can't be null (no default behavior of using local
166+
* hostname when addr is null).
167+
*
168+
* @param principalConfig
169+
* Kerberos principal name pattern to convert
170+
* @param addr
171+
* InetAddress of the host used for substitution
172+
* @return converted Kerberos principal name
173+
* @throws IOException
174+
*/
175+
public static String getServerPrincipal(String principalConfig,
176+
InetAddress addr) throws IOException {
177+
String[] components = getComponents(principalConfig);
178+
if (components == null || components.length != 3
179+
|| !components[1].equals(HOSTNAME_PATTERN)) {
168180
return principalConfig;
181+
} else {
182+
if (addr == null) {
183+
throw new IOException("Can't replace " + HOSTNAME_PATTERN
184+
+ " pattern since client address is null");
185+
}
186+
return replacePattern(components, addr.getCanonicalHostName());
187+
}
188+
}
189+
190+
private static String[] getComponents(String principalConfig) {
191+
if (principalConfig == null)
192+
return null;
193+
return principalConfig.split("[/@]");
194+
}
195+
196+
private static String replacePattern(String[] components, String hostname)
197+
throws IOException {
198+
String fqdn = hostname;
199+
if (fqdn == null || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
200+
fqdn = getLocalHostName();
169201
}
202+
return components[0] + "/" + fqdn + "@" + components[2];
170203
}
171204

172205
static String getLocalHostName() throws UnknownHostException {

src/java/org/apache/hadoop/security/authorize/ServiceAuthorizationManager.java

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.security.authorize;
1919

2020
import java.io.IOException;
21+
import java.net.InetAddress;
2122
import java.util.IdentityHashMap;
2223
import java.util.Map;
2324
import java.util.Set;
@@ -30,7 +31,6 @@
3031
import org.apache.hadoop.fs.CommonConfigurationKeys;
3132
import org.apache.hadoop.security.KerberosInfo;
3233
import org.apache.hadoop.security.SecurityUtil;
33-
import org.apache.hadoop.security.KerberosName;
3434
import org.apache.hadoop.security.UserGroupInformation;
3535

3636
/**
@@ -71,13 +71,13 @@ public class ServiceAuthorizationManager {
7171
* @param user user accessing the service
7272
* @param protocol service being accessed
7373
* @param conf configuration to use
74-
* @param hostname fully qualified domain name of the client
74+
* @param addr InetAddress of the client
7575
* @throws AuthorizationException on authorization failure
7676
*/
7777
public void authorize(UserGroupInformation user,
7878
Class<?> protocol,
7979
Configuration conf,
80-
String hostname
80+
InetAddress addr
8181
) throws AuthorizationException {
8282
AccessControlList acl = protocolToAcl.get(protocol);
8383
if (acl == null) {
@@ -91,41 +91,24 @@ public void authorize(UserGroupInformation user,
9191
if (krbInfo != null) {
9292
String clientKey = krbInfo.clientPrincipal();
9393
if (clientKey != null && !clientKey.equals("")) {
94-
if (hostname == null) {
95-
throw new AuthorizationException(
96-
"Can't authorize client when client hostname is null");
97-
}
9894
try {
9995
clientPrincipal = SecurityUtil.getServerPrincipal(
100-
conf.get(clientKey), hostname);
96+
conf.get(clientKey), addr);
10197
} catch (IOException e) {
10298
throw (AuthorizationException) new AuthorizationException(
10399
"Can't figure out Kerberos principal name for connection from "
104-
+ hostname + " for user=" + user + " protocol=" + protocol)
100+
+ addr + " for user=" + user + " protocol=" + protocol)
105101
.initCause(e);
106102
}
107103
}
108104
}
109-
// when authorizing use the short name only
110-
String shortName = clientPrincipal;
111-
if(clientPrincipal != null ) {
112-
try {
113-
shortName = new KerberosName(clientPrincipal).getShortName();
114-
} catch (IOException e) {
115-
LOG.warn("couldn't get short name from " + clientPrincipal, e);
116-
// just keep going
117-
}
118-
}
119-
if(LOG.isDebugEnabled()) {
120-
LOG.debug("for protocol authorization compare (" + clientPrincipal +
121-
"): " + shortName + " with " + user.getShortUserName());
122-
}
123-
if((shortName != null && !shortName.equals(user.getShortUserName())) ||
105+
if((clientPrincipal != null && !clientPrincipal.equals(user.getUserName())) ||
124106
!acl.isUserAllowed(user)) {
125-
AUDITLOG.warn(AUTHZ_FAILED_FOR + user + " for protocol="+protocol);
107+
AUDITLOG.warn(AUTHZ_FAILED_FOR + user + " for protocol=" + protocol
108+
+ ", expected client Kerberos principal is " + clientPrincipal);
126109
throw new AuthorizationException("User " + user +
127-
" is not authorized for protocol " +
128-
protocol);
110+
" is not authorized for protocol " + protocol +
111+
", expected client Kerberos principal is " + clientPrincipal);
129112
}
130113
AUDITLOG.info(AUTHZ_SUCCESSFULL_FOR + user + " for protocol="+protocol);
131114
}

src/test/core/org/apache/hadoop/security/TestSecurityUtil.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818

1919
import static org.junit.Assert.assertFalse;
2020
import static org.junit.Assert.assertTrue;
21+
import static org.junit.Assert.assertEquals;
2122

2223
import java.io.IOException;
24+
import java.net.InetAddress;
2325

2426
import javax.security.auth.kerberos.KerberosPrincipal;
2527

2628
import org.apache.hadoop.conf.Configuration;
27-
import org.junit.Assert;
2829
import org.junit.Test;
30+
import org.mockito.Mockito;
2931

3032
public class TestSecurityUtil {
3133
@Test
@@ -50,28 +52,47 @@ public void isOriginalTGTReturnsCorrectValues() {
5052

5153
private void verify(String original, String hostname, String expected)
5254
throws IOException {
53-
assertTrue(SecurityUtil.getServerPrincipal(original, hostname).equals(
54-
expected));
55-
assertTrue(SecurityUtil.getServerPrincipal(original, null).equals(
56-
expected));
57-
assertTrue(SecurityUtil.getServerPrincipal(original, "").equals(
58-
expected));
59-
assertTrue(SecurityUtil.getServerPrincipal(original, "0.0.0.0").equals(
60-
expected));
55+
assertEquals(expected,
56+
SecurityUtil.getServerPrincipal(original, hostname));
57+
58+
InetAddress addr = mockAddr(hostname);
59+
assertEquals(expected,
60+
SecurityUtil.getServerPrincipal(original, addr));
6161
}
6262

63+
private InetAddress mockAddr(String reverseTo) {
64+
InetAddress mock = Mockito.mock(InetAddress.class);
65+
Mockito.doReturn(reverseTo).when(mock).getCanonicalHostName();
66+
return mock;
67+
}
68+
6369
@Test
6470
public void testGetServerPrincipal() throws IOException {
6571
String service = "hdfs/";
6672
String realm = "@REALM";
67-
String hostname = SecurityUtil.getLocalHostName();
73+
String hostname = "foohost";
74+
String userPrincipal = "foo@FOOREALM";
6875
String shouldReplace = service + SecurityUtil.HOSTNAME_PATTERN + realm;
6976
String replaced = service + hostname + realm;
7077
verify(shouldReplace, hostname, replaced);
7178
String shouldNotReplace = service + SecurityUtil.HOSTNAME_PATTERN + "NAME"
7279
+ realm;
7380
verify(shouldNotReplace, hostname, shouldNotReplace);
74-
verify(shouldNotReplace, shouldNotReplace, shouldNotReplace);
81+
verify(userPrincipal, hostname, userPrincipal);
82+
// testing reverse DNS lookup doesn't happen
83+
InetAddress notUsed = Mockito.mock(InetAddress.class);
84+
assertEquals(shouldNotReplace,
85+
SecurityUtil.getServerPrincipal(shouldNotReplace, notUsed));
86+
Mockito.verify(notUsed, Mockito.never()).getCanonicalHostName();
87+
}
88+
89+
@Test
90+
public void testLocalHostNameForNullOrWild() throws Exception {
91+
String local = SecurityUtil.getLocalHostName();
92+
assertEquals("hdfs/" + local + "@REALM",
93+
SecurityUtil.getServerPrincipal("hdfs/_HOST@REALM", (String)null));
94+
assertEquals("hdfs/" + local + "@REALM",
95+
SecurityUtil.getServerPrincipal("hdfs/_HOST@REALM", "0.0.0.0"));
7596
}
7697

7798
@Test

0 commit comments

Comments
 (0)