1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.ipc;
19
20 import io.netty.util.HashedWheelTimer;
21 import io.netty.util.Timeout;
22 import io.netty.util.TimerTask;
23
24 import java.io.IOException;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.net.InetAddress;
28 import java.net.InetSocketAddress;
29 import java.net.UnknownHostException;
30 import java.util.concurrent.TimeUnit;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.hbase.HConstants;
36 import org.apache.hadoop.hbase.classification.InterfaceAudience;
37 import org.apache.hadoop.hbase.client.MetricsConnection;
38 import org.apache.hadoop.hbase.codec.Codec;
39 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
40 import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
41 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ConnectionHeader;
42 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.UserInformation;
43 import org.apache.hadoop.hbase.security.AuthMethod;
44 import org.apache.hadoop.hbase.security.SecurityInfo;
45 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
46 import org.apache.hadoop.io.Text;
47 import org.apache.hadoop.io.compress.CompressionCodec;
48 import org.apache.hadoop.security.SecurityUtil;
49 import org.apache.hadoop.security.UserGroupInformation;
50 import org.apache.hadoop.security.token.Token;
51 import org.apache.hadoop.security.token.TokenIdentifier;
52 import org.apache.hadoop.security.token.TokenSelector;
53 import org.apache.hadoop.util.Time;
54
55
56
57
58 @InterfaceAudience.Private
59 abstract class RpcConnection {
60
61 private static final Log LOG = LogFactory.getLog(RpcConnection.class);
62
63 protected final ConnectionId remoteId;
64
65 protected final AuthMethod authMethod;
66
67 protected final boolean useSasl;
68
69 protected final Token<? extends TokenIdentifier> token;
70
71 protected final String serverPrincipal;
72
73 protected final int reloginMaxBackoff;
74
75 protected final Codec codec;
76
77 protected final CompressionCodec compressor;
78
79 protected final MetricsConnection metrics;
80
81 protected final HashedWheelTimer timeoutTimer;
82
83
84 protected long lastTouched;
85
86
87 private boolean forceReloginEnabled;
88
89 private int minTimeBeforeForceRelogin;
90
91
92 private long lastForceReloginAttempt = -1;
93
94 protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, ConnectionId remoteId,
95 String clusterId, boolean isSecurityEnabled, Codec codec, CompressionCodec compressor,
96 MetricsConnection metrics) throws IOException {
97 this.timeoutTimer = timeoutTimer;
98 this.codec = codec;
99 this.compressor = compressor;
100 this.metrics = metrics;
101 UserGroupInformation ticket = remoteId.getTicket().getUGI();
102 SecurityInfo securityInfo = SecurityInfo.getInfo(remoteId.getServiceName());
103 this.useSasl = isSecurityEnabled;
104 Token<? extends TokenIdentifier> token = null;
105 String serverPrincipal = null;
106 if (useSasl && securityInfo != null) {
107 AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind();
108 if (tokenKind != null) {
109 TokenSelector<? extends TokenIdentifier> tokenSelector = AbstractRpcClient.TOKEN_HANDLERS
110 .get(tokenKind);
111 if (tokenSelector != null) {
112 token = tokenSelector.selectToken(new Text(clusterId), ticket.getTokens());
113 } else if (LOG.isDebugEnabled()) {
114 LOG.debug("No token selector found for type " + tokenKind);
115 }
116 }
117 String serverKey = securityInfo.getServerPrincipal();
118 if (serverKey == null) {
119 throw new IOException("Can't obtain server Kerberos config key from SecurityInfo");
120 }
121 if (metrics != null) {
122 metrics.incrNsLookups();
123 }
124 InetSocketAddress remoteAddr = remoteId.getAddress().toSocketAddress();
125 if (remoteAddr.isUnresolved()) {
126 if (metrics != null) {
127 metrics.incrNsLookupsFailed();
128 }
129 throw new UnknownHostException(remoteId.getAddress() + " could not be resolved");
130 }
131 serverPrincipal = SecurityUtil.getServerPrincipal(conf.get(serverKey),
132 getHostnameForServerPrincipal(conf, remoteAddr.getAddress()));
133 if (LOG.isDebugEnabled()) {
134 LOG.debug("RPC Server Kerberos principal name for service=" + remoteId.getServiceName()
135 + " is " + serverPrincipal);
136 }
137 }
138 this.token = token;
139 this.serverPrincipal = serverPrincipal;
140 if (!useSasl) {
141 authMethod = AuthMethod.SIMPLE;
142 } else if (token != null) {
143 authMethod = AuthMethod.DIGEST;
144 } else {
145 authMethod = AuthMethod.KERBEROS;
146 }
147
148 if (LOG.isDebugEnabled()) {
149 LOG.debug("Use " + authMethod + " authentication for service " + remoteId.serviceName
150 + ", sasl=" + useSasl);
151 }
152
153 reloginMaxBackoff = conf.getInt(HConstants.HBASE_RELOGIN_MAXBACKOFF, 5000);
154 this.remoteId = remoteId;
155
156 forceReloginEnabled = conf.getBoolean(HConstants.HBASE_FORCE_RELOGIN_ENABLED, true);
157
158 this.minTimeBeforeForceRelogin =
159 conf.getInt(HConstants.HBASE_MINTIME_BEFORE_FORCE_RELOGIN, 10 * 60 * 1000);
160 }
161
162 private static boolean useCanonicalHostname(Configuration conf) {
163 return !conf.getBoolean(
164 HConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS,
165 HConstants.DEFAULT_UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS);
166 }
167
168 public static String getHostnameForServerPrincipal(Configuration conf, InetAddress addr) {
169 final String hostname;
170
171 if (useCanonicalHostname(conf)) {
172 hostname = addr.getCanonicalHostName();
173 if (hostname.equals(addr.getHostAddress())) {
174 LOG.warn("Canonical hostname for SASL principal is the same with IP address: "
175 + hostname + ", " + addr.getHostName() + ". Check DNS configuration or consider "
176 + HConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS
177 + "=true");
178 }
179 } else {
180 hostname = addr.getHostName();
181 }
182
183 return hostname.toLowerCase();
184 }
185
186 private UserInformation getUserInfo(UserGroupInformation ugi) {
187 if (ugi == null || authMethod == AuthMethod.DIGEST) {
188
189 return null;
190 }
191 UserInformation.Builder userInfoPB = UserInformation.newBuilder();
192 if (authMethod == AuthMethod.KERBEROS) {
193
194 userInfoPB.setEffectiveUser(ugi.getUserName());
195 } else if (authMethod == AuthMethod.SIMPLE) {
196
197 userInfoPB.setEffectiveUser(ugi.getUserName());
198 if (ugi.getRealUser() != null) {
199 userInfoPB.setRealUser(ugi.getRealUser().getUserName());
200 }
201 }
202 return userInfoPB.build();
203 }
204
205 protected UserGroupInformation getUGI() {
206 UserGroupInformation ticket = remoteId.getTicket().getUGI();
207 if (authMethod == AuthMethod.KERBEROS) {
208 if (ticket != null && ticket.getRealUser() != null) {
209 ticket = ticket.getRealUser();
210 }
211 }
212 return ticket;
213 }
214
215 protected boolean shouldAuthenticateOverKrb() throws IOException {
216 UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
217 UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
218 UserGroupInformation realUser = currentUser.getRealUser();
219 return authMethod == AuthMethod.KERBEROS && loginUser != null &&
220
221 loginUser.hasKerberosCredentials() &&
222
223
224 (loginUser.equals(currentUser) || loginUser.equals(realUser));
225 }
226
227 protected void relogin() throws IOException {
228 if (UserGroupInformation.isLoginKeytabBased()) {
229 if (shouldForceRelogin()) {
230 LOG.debug(
231 "SASL Authentication failure. Attempting a forceful re-login for "
232 + UserGroupInformation.getLoginUser().getUserName());
233 Method logoutUserFromKeytab;
234 Method forceReloginFromKeytab;
235 try {
236 logoutUserFromKeytab = UserGroupInformation.class.getMethod("logoutUserFromKeytab");
237 forceReloginFromKeytab = UserGroupInformation.class.getMethod("forceReloginFromKeytab");
238 } catch (NoSuchMethodException e) {
239
240
241 throw new RuntimeException("Cannot find forceReloginFromKeytab method in UGI");
242 }
243 logoutUserFromKeytab.setAccessible(true);
244 forceReloginFromKeytab.setAccessible(true);
245 try {
246 logoutUserFromKeytab.invoke(UserGroupInformation.getLoginUser());
247 forceReloginFromKeytab.invoke(UserGroupInformation.getLoginUser());
248 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
249 throw new RuntimeException(e.getCause());
250 }
251 } else {
252 UserGroupInformation.getLoginUser().reloginFromKeytab();
253 }
254 } else {
255 UserGroupInformation.getLoginUser().reloginFromTicketCache();
256 }
257 }
258
259 private boolean shouldForceRelogin() {
260 if (!forceReloginEnabled) {
261 return false;
262 }
263 long now = Time.now();
264
265
266 if (lastForceReloginAttempt != -1
267 && (now - lastForceReloginAttempt < minTimeBeforeForceRelogin)) {
268 LOG.debug("Not attempting to force re-login since the last attempt is less than "
269 + minTimeBeforeForceRelogin + " millis");
270 return false;
271 }
272 try {
273
274 UserGroupInformation.class.getMethod("forceReloginFromKeytab");
275 UserGroupInformation.class.getMethod("logoutUserFromKeytab");
276 } catch (NoSuchMethodException e) {
277 LOG.debug(
278 "forceReloginFromKeytab method not available in UGI. Skipping to attempt force relogin");
279 return false;
280 }
281 lastForceReloginAttempt = now;
282 return true;
283 }
284
285 protected void scheduleTimeoutTask(final Call call) {
286 if (call.timeout > 0) {
287 call.timeoutTask = timeoutTimer.newTimeout(new TimerTask() {
288
289 @Override
290 public void run(Timeout timeout) throws Exception {
291 call.setTimeout(new CallTimeoutException("Call id=" + call.id + ", waitTime="
292 + (EnvironmentEdgeManager.currentTime() - call.getStartTime()) + ", rpcTimetout="
293 + call.timeout));
294 callTimeout(call);
295 }
296 }, call.timeout, TimeUnit.MILLISECONDS);
297 }
298 }
299
300 protected byte[] getConnectionHeaderPreamble() {
301
302
303
304
305
306 int rpcHeaderLen = HConstants.RPC_HEADER.length;
307 byte[] preamble = new byte[rpcHeaderLen + 2];
308 System.arraycopy(HConstants.RPC_HEADER, 0, preamble, 0, rpcHeaderLen);
309 preamble[rpcHeaderLen] = HConstants.RPC_CURRENT_VERSION;
310 synchronized (this) {
311 preamble[rpcHeaderLen + 1] = authMethod.code;
312 }
313 return preamble;
314 }
315
316 protected ConnectionHeader getConnectionHeader() {
317 ConnectionHeader.Builder builder = ConnectionHeader.newBuilder();
318 builder.setServiceName(remoteId.getServiceName());
319 UserInformation userInfoPB;
320 if ((userInfoPB = getUserInfo(remoteId.ticket.getUGI())) != null) {
321 builder.setUserInfo(userInfoPB);
322 }
323 if (this.codec != null) {
324 builder.setCellBlockCodecClass(this.codec.getClass().getCanonicalName());
325 }
326 if (this.compressor != null) {
327 builder.setCellBlockCompressorClass(this.compressor.getClass().getCanonicalName());
328 }
329 builder.setVersionInfo(ProtobufUtil.getVersionInfo());
330 return builder.build();
331 }
332
333 protected abstract void callTimeout(Call call);
334
335 public ConnectionId remoteId() {
336 return remoteId;
337 }
338
339 public long getLastTouched() {
340 return lastTouched;
341 }
342
343 public void setLastTouched(long lastTouched) {
344 this.lastTouched = lastTouched;
345 }
346
347
348
349
350 public abstract boolean isActive();
351
352
353
354
355 public abstract void shutdown();
356
357 public abstract void sendRequest(Call call, HBaseRpcController hrc) throws IOException;
358
359
360
361
362 public abstract void cleanupConnection();
363 }