RECORDING_TRUSTED_CHANNEL_FAILURE =
+ ThreadLocal.withInitial(() -> false);
+
public static final String OBJECT_AUTHENTICATION_AUDIT_STR =
"User %s (ID=%d) requests authority on object %s with result %s";
public static final String AUDIT_LOG_NODE_ID = "node_id";
@@ -61,4 +72,68 @@ public void recordObjectAuthenticationAuditLog(
auditObject.get(),
auditEntity.getResult()));
}
+
+ /**
+ * Records a failure of the trusted-channel function.
+ *
+ * The caller determines the channel direction and supplies the actual initiator and target
+ * identifiers. This keeps the audit hook independent of any concrete SSL/TLS implementation.
+ */
+ public void recordTrustedChannelFailureAuditLog(
+ final IAuditEntity auditEntity,
+ final Supplier initiator,
+ final Supplier target) {
+ log(
+ auditEntity
+ .setAuditEventType(AuditEventType.TRUSTED_CHANNEL_FUNCTION_FAILURE)
+ .setAuditLogOperation(AuditLogOperation.CONTROL)
+ .setResult(false),
+ () ->
+ String.format(
+ CommonMessages
+ .LOG_TRUSTED_CHANNEL_FUNCTION_FAILED_INITIATOR_ARG_TARGET_ARG_E4C28443,
+ initiator.get(),
+ target.get()));
+ }
+
+ public static boolean isSslFailure(Throwable failure) {
+ Throwable cause = failure;
+ while (cause != null) {
+ if (cause instanceof SSLException) {
+ return true;
+ }
+ cause = cause.getCause();
+ }
+ return false;
+ }
+
+ public void recordTrustedChannelFailureAuditLogIfNecessary(
+ Throwable failure, TEndPoint initiator, TEndPoint target) {
+ if (RECORDING_TRUSTED_CHANNEL_FAILURE.get()
+ || !isSslFailure(failure)
+ || initiator == null
+ || target == null) {
+ return;
+ }
+
+ final String initiatorIdentifier = NodeUrlUtils.convertTEndPointUrl(initiator);
+ final String targetIdentifier = NodeUrlUtils.convertTEndPointUrl(target);
+ RECORDING_TRUSTED_CHANNEL_FAILURE.set(true);
+ try {
+ recordTrustedChannelFailureAuditLog(
+ new UserEntity(
+ INTERNAL_AUDIT_LOG_USER_ID,
+ User.BUILTIN_INTERNAL_AUDIT_LOG_USERNAME,
+ initiatorIdentifier)
+ .setPrivilegeType(PrivilegeType.AUDIT),
+ () -> initiatorIdentifier,
+ () -> targetIdentifier);
+ } catch (RuntimeException auditFailure) {
+ if (auditFailure != failure) {
+ failure.addSuppressed(auditFailure);
+ }
+ } finally {
+ RECORDING_TRUSTED_CHANNEL_FAILURE.remove();
+ }
+ }
}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java
index 510912a302fdc..2c99e436676b8 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/AuditEventType.java
@@ -44,7 +44,7 @@ public enum AuditEventType {
LOGIN_EXCEED_LIMIT,
SESSION_TIME_EXCEEDED,
LOGIN_REJECT_IP,
- SESSION_ENCRYPT_FAILED,
+ TRUSTED_CHANNEL_FUNCTION_FAILURE,
SYSTEM_OPERATION,
DN_SHUTDOWN;
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/TrustedChannelFailureHandler.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/TrustedChannelFailureHandler.java
new file mode 100644
index 0000000000000..e342cc8d231b2
--- /dev/null
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/TrustedChannelFailureHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.audit;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+
+@FunctionalInterface
+public interface TrustedChannelFailureHandler {
+
+ TrustedChannelFailureHandler NO_OP = (failure, initiator, target) -> {};
+
+ void onFailure(Throwable failure, TEndPoint initiator, TEndPoint target);
+}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/async/AsyncIoTConsensusV2ServiceClient.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/async/AsyncIoTConsensusV2ServiceClient.java
index cc8b6f6e1bfbf..0214467dbdf47 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/async/AsyncIoTConsensusV2ServiceClient.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/async/AsyncIoTConsensusV2ServiceClient.java
@@ -26,6 +26,7 @@
import org.apache.iotdb.commons.client.property.ThriftClientProperty;
import org.apache.iotdb.commons.conf.CommonConfig;
import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.consensus.iotv2.container.IoTV2GlobalComponentContainer;
import org.apache.iotdb.commons.i18n.ClientMessages;
import org.apache.iotdb.consensus.iotconsensusv2.thrift.IoTConsensusV2IService;
import org.apache.iotdb.rpc.TNonblockingTransportWrapper;
@@ -89,6 +90,7 @@ public void onComplete() {
@Override
public void onError(Exception e) {
+ IoTV2GlobalComponentContainer.getInstance().reportTrustedChannelFailure(e, endpoint);
super.onError(e);
ThriftClient.resolveException(e, this);
returnSelf();
@@ -167,12 +169,17 @@ public void destroyObject(
@Override
public PooledObject makeObject(TEndPoint endPoint)
throws Exception {
- return new DefaultPooledObject<>(
- new AsyncIoTConsensusV2ServiceClient(
- thriftClientProperty,
- endPoint,
- tManagers[Math.floorMod(clientCnt.incrementAndGet(), tManagers.length)],
- clientManager));
+ try {
+ return new DefaultPooledObject<>(
+ new AsyncIoTConsensusV2ServiceClient(
+ thriftClientProperty,
+ endPoint,
+ tManagers[Math.floorMod(clientCnt.incrementAndGet(), tManagers.length)],
+ clientManager));
+ } catch (final Exception e) {
+ IoTV2GlobalComponentContainer.getInstance().reportTrustedChannelFailure(e, endPoint);
+ throw e;
+ }
}
@Override
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/request/AsyncRequestManager.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/request/AsyncRequestManager.java
index b13d1ab06373d..e8a47db01f640 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/request/AsyncRequestManager.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/request/AsyncRequestManager.java
@@ -210,6 +210,13 @@ protected void sendAsyncRequest(
endPoint,
e.getMessage(),
retryCount);
+ try {
+ onRequestFailure(e, endPoint);
+ } catch (final RuntimeException reportingFailure) {
+ if (reportingFailure != e) {
+ e.addSuppressed(reportingFailure);
+ }
+ }
if (handler != null) {
try {
handler.onError(e);
@@ -237,6 +244,10 @@ protected void adjustClientTimeoutIfNecessary(RequestType type, Client client, L
// In default, no need to do this
}
+ protected void onRequestFailure(Exception failure, TEndPoint targetEndPoint) {
+ // In default, no need to do this
+ }
+
protected abstract TEndPoint nodeLocationToEndPoint(NodeLocation location);
protected abstract AsyncRequestRPCHandler, RequestType, NodeLocation> buildHandler(
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncIoTConsensusV2ServiceClient.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncIoTConsensusV2ServiceClient.java
index 0b74424277593..caae534840052 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncIoTConsensusV2ServiceClient.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncIoTConsensusV2ServiceClient.java
@@ -26,6 +26,7 @@
import org.apache.iotdb.commons.client.property.ThriftClientProperty;
import org.apache.iotdb.commons.conf.CommonConfig;
import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.consensus.iotv2.container.IoTV2GlobalComponentContainer;
import org.apache.iotdb.consensus.iotconsensusv2.thrift.IoTConsensusV2IService;
import org.apache.iotdb.rpc.DeepCopyRpcTransportFactory;
import org.apache.iotdb.rpc.TConfigurationConst;
@@ -135,14 +136,22 @@ public void destroyObject(
@Override
public PooledObject makeObject(TEndPoint endpoint)
throws Exception {
- return new DefaultPooledObject<>(
- SyncThriftClientWithErrorHandler.newErrorHandler(
- SyncIoTConsensusV2ServiceClient.class,
- SyncIoTConsensusV2ServiceClient.class.getConstructor(
- thriftClientProperty.getClass(), endpoint.getClass(), clientManager.getClass()),
- thriftClientProperty,
- endpoint,
- clientManager));
+ try {
+ return new DefaultPooledObject<>(
+ SyncThriftClientWithErrorHandler.newErrorHandlerWithFailureHandler(
+ SyncIoTConsensusV2ServiceClient.class,
+ SyncIoTConsensusV2ServiceClient.class.getConstructor(
+ thriftClientProperty.getClass(), endpoint.getClass(), clientManager.getClass()),
+ (failure, client) ->
+ IoTV2GlobalComponentContainer.getInstance()
+ .reportTrustedChannelFailure(failure, client.getTEndpoint()),
+ thriftClientProperty,
+ endpoint,
+ clientManager));
+ } catch (final Exception e) {
+ IoTV2GlobalComponentContainer.getInstance().reportTrustedChannelFailure(e, endpoint);
+ throw e;
+ }
}
@Override
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncThriftClientWithErrorHandler.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncThriftClientWithErrorHandler.java
index f85d1421547f5..77b25895cb2f6 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncThriftClientWithErrorHandler.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/sync/SyncThriftClientWithErrorHandler.java
@@ -29,9 +29,20 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.function.BiConsumer;
public class SyncThriftClientWithErrorHandler implements MethodInterceptor {
+ private static final BiConsumer NO_OP_FAILURE_HANDLER =
+ (failure, client) -> {};
+
+ private final BiConsumer failureHandler;
+
+ private SyncThriftClientWithErrorHandler(
+ final BiConsumer failureHandler) {
+ this.failureHandler = failureHandler;
+ }
+
/**
* Note: The caller needs to ensure that the constructor corresponds to the class, or the cast
* might fail.
@@ -39,9 +50,31 @@ public class SyncThriftClientWithErrorHandler implements MethodInterceptor {
@SuppressWarnings("unchecked")
public static V newErrorHandler(
Class targetClass, Constructor constructor, Object... args) {
+ return newErrorHandler(targetClass, constructor, NO_OP_FAILURE_HANDLER, args);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static V newErrorHandlerWithFailureHandler(
+ Class targetClass,
+ Constructor constructor,
+ BiConsumer failureHandler,
+ Object... args) {
+ return newErrorHandler(
+ targetClass,
+ constructor,
+ (failure, client) -> failureHandler.accept(failure, (V) client),
+ args);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static V newErrorHandler(
+ Class targetClass,
+ Constructor constructor,
+ BiConsumer failureHandler,
+ Object... args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
- enhancer.setCallback(new SyncThriftClientWithErrorHandler());
+ enhancer.setCallback(new SyncThriftClientWithErrorHandler(failureHandler));
if (constructor == null) {
return (V) enhancer.create();
}
@@ -54,6 +87,13 @@ public Object intercept(Object o, Method method, Object[] objects, MethodProxy m
try {
return methodProxy.invokeSuper(o, objects);
} catch (Throwable t) {
+ try {
+ failureHandler.accept(t, (ThriftClient) o);
+ } catch (final RuntimeException reportingFailure) {
+ if (reportingFailure != t) {
+ t.addSuppressed(reportingFailure);
+ }
+ }
ThriftClient.resolveException(t, (ThriftClient) o);
throw new TException(
ClientMessages.EXCEPTION_ERROR_CALLING_METHOD_C04E5A63
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/iotv2/container/IoTV2GlobalComponentContainer.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/iotv2/container/IoTV2GlobalComponentContainer.java
index fdf34e434cc85..bb63be960fc6d 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/iotv2/container/IoTV2GlobalComponentContainer.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/iotv2/container/IoTV2GlobalComponentContainer.java
@@ -20,6 +20,7 @@
package org.apache.iotdb.commons.consensus.iotv2.container;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.audit.TrustedChannelFailureHandler;
import org.apache.iotdb.commons.client.ClientPoolFactory.AsyncIoTConsensusV2ServiceClientPoolFactory;
import org.apache.iotdb.commons.client.ClientPoolFactory.SyncIoTConsensusV2ServiceClientPoolFactory;
import org.apache.iotdb.commons.client.IClientManager;
@@ -36,6 +37,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -54,6 +56,9 @@ public class IoTV2GlobalComponentContainer {
private final IClientManager asyncClientManager;
private final IClientManager syncClientManager;
private final ScheduledExecutorService backgroundTaskService;
+ private volatile TEndPoint thisNode;
+ private volatile TrustedChannelFailureHandler trustedChannelFailureHandler =
+ TrustedChannelFailureHandler.NO_OP;
private PipeSubtaskExecutor consensusExecutor;
private IoTV2GlobalComponentContainer() {
@@ -87,6 +92,26 @@ public ScheduledExecutorService getBackgroundTaskService() {
return this.backgroundTaskService;
}
+ public void configureTrustedChannelFailureHandler(
+ final TEndPoint thisNode, final TrustedChannelFailureHandler trustedChannelFailureHandler) {
+ this.thisNode = Objects.requireNonNull(thisNode);
+ this.trustedChannelFailureHandler = Objects.requireNonNull(trustedChannelFailureHandler);
+ }
+
+ public void reportTrustedChannelFailure(final Throwable failure, final TEndPoint targetEndPoint) {
+ final TEndPoint initiatorEndPoint = thisNode;
+ if (failure == null || initiatorEndPoint == null || targetEndPoint == null) {
+ return;
+ }
+ try {
+ trustedChannelFailureHandler.onFailure(failure, initiatorEndPoint, targetEndPoint);
+ } catch (final RuntimeException reportingFailure) {
+ if (reportingFailure != failure) {
+ failure.addSuppressed(reportingFailure);
+ }
+ }
+ }
+
public void stopBackgroundTaskService() {
backgroundTaskService.shutdownNow();
try {
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManager.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManager.java
index 83b85a6d58303..d34f84fe9d685 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManager.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManager.java
@@ -33,6 +33,7 @@
import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp;
+import org.apache.thrift.transport.TTransportException;
import org.apache.tsfile.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -200,22 +201,10 @@ protected void reconstructClient(TEndPoint endPoint) {
private boolean initClientAndStatus(
final Pair clientAndStatus, final TEndPoint endPoint) {
try {
- clientAndStatus.setLeft(
- new IoTDBSyncClient(
- new ThriftClientProperty.Builder()
- .setConnectionTimeoutMs(PIPE_CONFIG.getPipeSinkHandshakeTimeoutMs())
- .setRpcThriftCompressionEnabled(
- PIPE_CONFIG.isPipeSinkRPCThriftCompressionEnabled())
- .build(),
- endPoint.getIp(),
- endPoint.getPort(),
- useSSL,
- trustStorePath,
- trustStorePwd,
- keyStorePath,
- keyStorePwd));
+ clientAndStatus.setLeft(createClient(endPoint));
return true;
} catch (Exception e) {
+ notifyClientConnectionFailure(e, endPoint);
endPoint2HandshakeErrorMessage.put(endPoint, e.getMessage());
PipeLogger.log(
LOGGER::warn,
@@ -228,6 +217,21 @@ private boolean initClientAndStatus(
}
}
+ protected IoTDBSyncClient createClient(final TEndPoint endPoint) throws TTransportException {
+ return new IoTDBSyncClient(
+ new ThriftClientProperty.Builder()
+ .setConnectionTimeoutMs(PIPE_CONFIG.getPipeSinkHandshakeTimeoutMs())
+ .setRpcThriftCompressionEnabled(PIPE_CONFIG.isPipeSinkRPCThriftCompressionEnabled())
+ .build(),
+ endPoint.getIp(),
+ endPoint.getPort(),
+ useSSL,
+ trustStorePath,
+ trustStorePwd,
+ keyStorePath,
+ keyStorePwd);
+ }
+
public void sendHandshakeReq(final Pair clientAndStatus) {
final IoTDBSyncClient client = clientAndStatus.getLeft();
try {
@@ -289,6 +293,7 @@ public void sendHandshakeReq(final Pair clientAndStatu
LOGGER.info(PipeMessages.HANDSHAKE_SUCCESS_TARGET, client.getIpAddress(), client.getPort());
}
} catch (Exception e) {
+ notifyClientConnectionFailure(e, client.getEndPoint());
LOGGER.warn(
PipeMessages.HANDSHAKE_ERROR_WITH_TARGET_SERVER,
client.getIpAddress(),
@@ -299,6 +304,22 @@ public void sendHandshakeReq(final Pair clientAndStatu
}
}
+ private void notifyClientConnectionFailure(
+ final Exception failure, final TEndPoint targetEndPoint) {
+ try {
+ onClientConnectionFailure(failure, targetEndPoint);
+ } catch (final RuntimeException reportingFailure) {
+ if (reportingFailure != failure) {
+ failure.addSuppressed(reportingFailure);
+ }
+ }
+ }
+
+ protected void onClientConnectionFailure(
+ final Exception failure, final TEndPoint targetEndPoint) {
+ // In default, no need to do this
+ }
+
protected abstract PipeTransferHandshakeV1Req buildHandshakeV1Req() throws IOException;
protected abstract PipeTransferHandshakeV2Req buildHandshakeV2Req(Map params)
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandler.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandler.java
new file mode 100644
index 0000000000000..c1063728c8f7d
--- /dev/null
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandler.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.service;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.audit.TrustedChannelFailureHandler;
+import org.apache.iotdb.rpc.TElasticFramedTransport;
+
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.server.ServerContext;
+import org.apache.thrift.server.TServerEventHandler;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Objects;
+
+/**
+ * Performs the server-side TLS handshake before the first Thrift request is processed and reports
+ * handshake failures together with the raw socket peer and the local service endpoint.
+ */
+public class TrustedChannelAuditServerEventHandler implements TServerEventHandler {
+
+ private final TServerEventHandler delegate;
+ private final TEndPoint target;
+ private final TrustedChannelFailureHandler failureHandler;
+
+ public TrustedChannelAuditServerEventHandler(
+ TServerEventHandler delegate, TEndPoint target, TrustedChannelFailureHandler failureHandler) {
+ this.delegate = Objects.requireNonNull(delegate);
+ this.target = Objects.requireNonNull(target);
+ this.failureHandler = Objects.requireNonNull(failureHandler);
+ }
+
+ @Override
+ public void preServe() {
+ delegate.preServe();
+ }
+
+ @Override
+ public ServerContext createContext(TProtocol input, TProtocol output) {
+ startHandshakeIfNecessary(output);
+ return delegate.createContext(input, output);
+ }
+
+ @Override
+ public void deleteContext(ServerContext serverContext, TProtocol input, TProtocol output) {
+ if (serverContext != null) {
+ delegate.deleteContext(serverContext, input, output);
+ }
+ }
+
+ @Override
+ public void processContext(
+ ServerContext serverContext, TTransport inputTransport, TTransport outputTransport) {
+ delegate.processContext(serverContext, inputTransport, outputTransport);
+ }
+
+ private void startHandshakeIfNecessary(TProtocol output) {
+ Socket socket = getSocket(output);
+ if (!(socket instanceof SSLSocket)) {
+ return;
+ }
+
+ try {
+ ((SSLSocket) socket).startHandshake();
+ } catch (IOException e) {
+ if (e instanceof SSLException) {
+ notifyFailure(e, socket.getRemoteSocketAddress());
+ }
+ try {
+ socket.close();
+ } catch (IOException closeFailure) {
+ if (closeFailure != e) {
+ e.addSuppressed(closeFailure);
+ }
+ }
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void notifyFailure(Throwable failure, SocketAddress remoteAddress) {
+ TEndPoint initiator = toEndPoint(remoteAddress);
+ if (initiator == null) {
+ return;
+ }
+ try {
+ failureHandler.onFailure(failure, initiator, target);
+ } catch (RuntimeException auditFailure) {
+ if (auditFailure != failure) {
+ failure.addSuppressed(auditFailure);
+ }
+ }
+ }
+
+ private static Socket getSocket(TProtocol protocol) {
+ if (protocol == null || !(protocol.getTransport() instanceof TElasticFramedTransport)) {
+ return null;
+ }
+ TTransport socketTransport = ((TElasticFramedTransport) protocol.getTransport()).getSocket();
+ return socketTransport instanceof TSocket ? ((TSocket) socketTransport).getSocket() : null;
+ }
+
+ private static TEndPoint toEndPoint(SocketAddress socketAddress) {
+ if (!(socketAddress instanceof InetSocketAddress)) {
+ return null;
+ }
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+ String host =
+ inetSocketAddress.getAddress() == null
+ ? inetSocketAddress.getHostString()
+ : inetSocketAddress.getAddress().getHostAddress();
+ return new TEndPoint(host, inetSocketAddress.getPort());
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/audit/AbstractAuditLoggerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/audit/AbstractAuditLoggerTest.java
new file mode 100644
index 0000000000000..73d0fb5fda130
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/audit/AbstractAuditLoggerTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.audit;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+
+import org.apache.thrift.TException;
+import org.junit.Test;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class AbstractAuditLoggerTest {
+
+ @Test
+ public void testRecordTrustedChannelFailureAuditLog() {
+ TestAuditLogger auditLogger = new TestAuditLogger();
+ UserEntity auditEntity = new UserEntity(0, "system", "127.0.0.1");
+ AtomicInteger identifierEvaluationCount = new AtomicInteger();
+
+ auditLogger.recordTrustedChannelFailureAuditLog(
+ auditEntity,
+ () -> {
+ identifierEvaluationCount.incrementAndGet();
+ return "DataNode-1@10.0.0.1:10730";
+ },
+ () -> {
+ identifierEvaluationCount.incrementAndGet();
+ return "DataNode-2@10.0.0.2:10730";
+ });
+
+ assertSame(auditEntity, auditLogger.auditEntity);
+ assertEquals(
+ AuditEventType.TRUSTED_CHANNEL_FUNCTION_FAILURE,
+ auditLogger.auditEntity.getAuditEventType());
+ assertEquals(AuditLogOperation.CONTROL, auditLogger.auditEntity.getAuditLogOperation());
+ assertFalse(auditLogger.auditEntity.getResult());
+ assertEquals(0, identifierEvaluationCount.get());
+ assertEquals(
+ "Trusted channel function failed: initiator=DataNode-1@10.0.0.1:10730, target=DataNode-2@10.0.0.2:10730",
+ auditLogger.auditLog.get());
+ assertEquals(2, identifierEvaluationCount.get());
+ }
+
+ @Test
+ public void testIsSslFailure() {
+ assertTrue(AbstractAuditLogger.isSslFailure(new SSLException("ssl failure")));
+ assertTrue(
+ AbstractAuditLogger.isSslFailure(
+ new TException(new IOException(new SSLHandshakeException("handshake failure")))));
+ assertFalse(
+ AbstractAuditLogger.isSslFailure(new TException(new IOException("network failure"))));
+ assertFalse(AbstractAuditLogger.isSslFailure(null));
+ }
+
+ @Test
+ public void testRecordTrustedChannelFailureAuditLogIfNecessary() {
+ TestAuditLogger auditLogger = new TestAuditLogger();
+ TEndPoint initiator = new TEndPoint("10.0.0.1", 10730);
+ TEndPoint target = new TEndPoint("10.0.0.2", 10730);
+
+ auditLogger.recordTrustedChannelFailureAuditLogIfNecessary(
+ new IOException("network failure"), initiator, target);
+ assertNull(auditLogger.auditEntity);
+
+ auditLogger.recordTrustedChannelFailureAuditLogIfNecessary(
+ new SSLHandshakeException("handshake failure"), initiator, target);
+ assertEquals(
+ AuditEventType.TRUSTED_CHANNEL_FUNCTION_FAILURE,
+ auditLogger.auditEntity.getAuditEventType());
+ assertEquals(
+ "Trusted channel function failed: initiator=10.0.0.1:10730, target=10.0.0.2:10730",
+ auditLogger.auditLog.get());
+ }
+
+ @Test
+ public void testAuditFailureShouldNotReplaceTrustedChannelFailure() {
+ final RuntimeException auditFailure = new RuntimeException("audit failure");
+ final ThrowingAuditLogger auditLogger = new ThrowingAuditLogger(auditFailure);
+ final SSLHandshakeException channelFailure = new SSLHandshakeException("handshake failure");
+ final TEndPoint initiator = new TEndPoint("10.0.0.1", 10730);
+ final TEndPoint target = new TEndPoint("10.0.0.2", 10730);
+
+ auditLogger.recordTrustedChannelFailureAuditLogIfNecessary(channelFailure, initiator, target);
+
+ assertEquals(1, auditLogger.invocationCount.get());
+ assertEquals(1, channelFailure.getSuppressed().length);
+ assertSame(auditFailure, channelFailure.getSuppressed()[0]);
+ }
+
+ private static class TestAuditLogger extends AbstractAuditLogger {
+
+ private IAuditEntity auditEntity;
+ private Supplier auditLog;
+
+ @Override
+ public void log(IAuditEntity auditLogFields, Supplier log) {
+ auditEntity = auditLogFields;
+ auditLog = log;
+ }
+ }
+
+ private static class ThrowingAuditLogger extends AbstractAuditLogger {
+
+ private final RuntimeException auditFailure;
+ private final AtomicInteger invocationCount = new AtomicInteger();
+
+ private ThrowingAuditLogger(final RuntimeException auditFailure) {
+ this.auditFailure = auditFailure;
+ }
+
+ @Override
+ public void log(final IAuditEntity auditLogFields, final Supplier log) {
+ invocationCount.incrementAndGet();
+ throw auditFailure;
+ }
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/client/request/AsyncRequestManagerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/client/request/AsyncRequestManagerTest.java
index 40b2bf9730b7e..28c076b8964b6 100644
--- a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/client/request/AsyncRequestManagerTest.java
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/client/request/AsyncRequestManagerTest.java
@@ -34,6 +34,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
public class AsyncRequestManagerTest {
@@ -83,6 +84,23 @@ public void handlerErrorFailureShouldNotEscapeDispatchFailure() throws Exception
Assert.assertEquals(Collections.singletonList(1), context.getRequestIndices());
}
+ @Test
+ public void reportingFailureShouldNotReplaceDispatchFailure() {
+ final TestAsyncRequestManager manager = new TestAsyncRequestManager(true, false, false, true);
+ final AsyncRequestContext context =
+ new AsyncRequestContext<>(
+ TestRequestType.TEST,
+ "request",
+ Collections.singletonMap(1, new TestNodeLocation(new TEndPoint("localhost", 6667))));
+
+ manager.sendAsyncRequest(context, 1, null, true);
+
+ Assert.assertEquals("borrow failed", context.getResponseMap().get(1));
+ Assert.assertEquals(1, manager.getReportedFailure().getSuppressed().length);
+ Assert.assertEquals(
+ "reporting failed", manager.getReportedFailure().getSuppressed()[0].getMessage());
+ }
+
private enum TestRequestType {
TEST
}
@@ -103,23 +121,38 @@ private static class TestAsyncRequestManager
private boolean failOnBorrow;
private boolean failOnDispatch;
private boolean failOnError;
+ private boolean failOnReporting;
+ private final AtomicReference reportedFailure = new AtomicReference<>();
private TestAsyncRequestManager() {
- this(true, false, false);
+ this(true, false, false, false);
}
private TestAsyncRequestManager(
final boolean failOnBorrow, final boolean failOnDispatch, final boolean failOnError) {
+ this(failOnBorrow, failOnDispatch, failOnError, false);
+ }
+
+ private TestAsyncRequestManager(
+ final boolean failOnBorrow,
+ final boolean failOnDispatch,
+ final boolean failOnError,
+ final boolean failOnReporting) {
super(1);
this.failOnBorrow = failOnBorrow;
this.failOnDispatch = failOnDispatch;
this.failOnError = failOnError;
+ this.failOnReporting = failOnReporting;
}
private int getBorrowAttempts() {
return borrowAttempts.get();
}
+ private Exception getReportedFailure() {
+ return reportedFailure.get();
+ }
+
@Override
protected void initClientManager(final int selectorNumOfAsyncClientManager) {
clientManager =
@@ -168,6 +201,14 @@ protected TEndPoint nodeLocationToEndPoint(final TestNodeLocation location) {
return location.endPoint;
}
+ @Override
+ protected void onRequestFailure(final Exception failure, final TEndPoint targetEndPoint) {
+ reportedFailure.set(failure);
+ if (failOnReporting) {
+ throw new RuntimeException("reporting failed");
+ }
+ }
+
@SuppressWarnings("unchecked")
@Override
protected AsyncRequestRPCHandler, TestRequestType, TestNodeLocation> buildHandler(
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManagerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManagerTest.java
new file mode 100644
index 0000000000000..ea2e4cad25894
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/sink/client/IoTDBSyncClientManagerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.sink.client;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.audit.UserEntity;
+import org.apache.iotdb.commons.pipe.sink.payload.thrift.request.PipeTransferHandshakeV1Req;
+import org.apache.iotdb.commons.pipe.sink.payload.thrift.request.PipeTransferHandshakeV2Req;
+
+import org.apache.thrift.transport.TTransportException;
+import org.junit.Test;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class IoTDBSyncClientManagerTest {
+
+ @Test
+ public void sslConnectionFailureShouldBeReportedWithoutReplacingPipeFailure() {
+ final TEndPoint target = new TEndPoint("127.0.0.1", 6667);
+ final TestIoTDBSyncClientManager clientManager = new TestIoTDBSyncClientManager(target);
+
+ clientManager.reconstruct(target);
+
+ assertSame(target, clientManager.reportedTarget);
+ assertTrue(clientManager.reportedFailure.getCause() instanceof SSLHandshakeException);
+ assertEquals(1, clientManager.reportedFailure.getSuppressed().length);
+ assertSame(clientManager.reportingFailure, clientManager.reportedFailure.getSuppressed()[0]);
+ }
+
+ private static class TestIoTDBSyncClientManager extends IoTDBSyncClientManager {
+
+ private final RuntimeException reportingFailure = new RuntimeException("reporting failed");
+ private Exception reportedFailure;
+ private TEndPoint reportedTarget;
+
+ private TestIoTDBSyncClientManager(final TEndPoint target) {
+ super(
+ Collections.singletonList(target),
+ true,
+ "trust-store",
+ "trust-store-password",
+ null,
+ null,
+ false,
+ "round-robin",
+ new UserEntity(0, "system", "127.0.0.1"),
+ "password",
+ false,
+ "sync",
+ false,
+ true,
+ false);
+ }
+
+ private void reconstruct(final TEndPoint target) {
+ reconstructClient(target);
+ }
+
+ @Override
+ protected IoTDBSyncClient createClient(final TEndPoint endPoint) throws TTransportException {
+ throw new TTransportException(new SSLHandshakeException("handshake failed"));
+ }
+
+ @Override
+ protected void onClientConnectionFailure(
+ final Exception failure, final TEndPoint targetEndPoint) {
+ reportedFailure = failure;
+ reportedTarget = targetEndPoint;
+ throw reportingFailure;
+ }
+
+ @Override
+ protected PipeTransferHandshakeV1Req buildHandshakeV1Req() throws IOException {
+ return null;
+ }
+
+ @Override
+ protected PipeTransferHandshakeV2Req buildHandshakeV2Req(final Map params)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ protected String getClusterId() {
+ return "test";
+ }
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandlerTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandlerTest.java
new file mode 100644
index 0000000000000..ede6d64cbc440
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/TrustedChannelAuditServerEventHandlerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.service;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.audit.TrustedChannelFailureHandler;
+import org.apache.iotdb.rpc.TElasticFramedTransport;
+
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.server.ServerContext;
+import org.apache.thrift.server.TServerEventHandler;
+import org.apache.thrift.transport.TSocket;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSocket;
+
+import java.io.UncheckedIOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TrustedChannelAuditServerEventHandlerTest {
+
+ @Test
+ public void testHandshakeBeforeCreatingDelegateContext() throws Exception {
+ TServerEventHandler delegate = mock(TServerEventHandler.class);
+ ServerContext context = mock(ServerContext.class);
+ TProtocol input = mock(TProtocol.class);
+ SSLSocket socket = mock(SSLSocket.class);
+ TProtocol output = createProtocol(socket);
+ TEndPoint target = new TEndPoint("10.0.0.2", 10730);
+ when(delegate.createContext(input, output)).thenReturn(context);
+
+ TrustedChannelAuditServerEventHandler handler =
+ new TrustedChannelAuditServerEventHandler(
+ delegate, target, TrustedChannelFailureHandler.NO_OP);
+
+ assertSame(context, handler.createContext(input, output));
+ InOrder inOrder = inOrder(socket, delegate);
+ inOrder.verify(socket).startHandshake();
+ inOrder.verify(delegate).createContext(input, output);
+ }
+
+ @Test
+ public void testHandshakeFailureReportsPeerAndSkipsDelegate() throws Exception {
+ TServerEventHandler delegate = mock(TServerEventHandler.class);
+ TProtocol input = mock(TProtocol.class);
+ SSLSocket socket = mock(SSLSocket.class);
+ TProtocol output = createProtocol(socket);
+ SSLHandshakeException failure = new SSLHandshakeException("handshake failure");
+ InetSocketAddress remoteAddress = new InetSocketAddress("192.0.2.10", 45123);
+ TEndPoint target = new TEndPoint("10.0.0.2", 10730);
+ AtomicReference reportedFailure = new AtomicReference<>();
+ AtomicReference reportedInitiator = new AtomicReference<>();
+ AtomicReference reportedTarget = new AtomicReference<>();
+ when(socket.getRemoteSocketAddress()).thenReturn(remoteAddress);
+ org.mockito.Mockito.doThrow(failure).when(socket).startHandshake();
+
+ TrustedChannelAuditServerEventHandler handler =
+ new TrustedChannelAuditServerEventHandler(
+ delegate,
+ target,
+ (throwable, initiator, endpoint) -> {
+ reportedFailure.set(throwable);
+ reportedInitiator.set(initiator);
+ reportedTarget.set(endpoint);
+ });
+
+ UncheckedIOException thrown =
+ assertThrows(UncheckedIOException.class, () -> handler.createContext(input, output));
+
+ assertSame(failure, thrown.getCause());
+ assertSame(failure, reportedFailure.get());
+ assertEquals(new TEndPoint("192.0.2.10", 45123), reportedInitiator.get());
+ assertSame(target, reportedTarget.get());
+ verify(socket).close();
+ verify(delegate, never()).createContext(any(), any());
+
+ handler.deleteContext(null, input, output);
+ verify(delegate, never()).deleteContext(isNull(), any(), any());
+ }
+
+ @Test
+ public void testCloseFailureDoesNotSelfSuppressHandshakeFailure() throws Exception {
+ TServerEventHandler delegate = mock(TServerEventHandler.class);
+ TProtocol input = mock(TProtocol.class);
+ SSLSocket socket = mock(SSLSocket.class);
+ TProtocol output = createProtocol(socket);
+ SSLHandshakeException failure = new SSLHandshakeException("handshake failure");
+ when(socket.getRemoteSocketAddress()).thenReturn(new InetSocketAddress("192.0.2.10", 45123));
+ org.mockito.Mockito.doThrow(failure).when(socket).startHandshake();
+ org.mockito.Mockito.doThrow(failure).when(socket).close();
+
+ TrustedChannelAuditServerEventHandler handler =
+ new TrustedChannelAuditServerEventHandler(
+ delegate, new TEndPoint("10.0.0.2", 10730), TrustedChannelFailureHandler.NO_OP);
+
+ UncheckedIOException thrown =
+ assertThrows(UncheckedIOException.class, () -> handler.createContext(input, output));
+
+ assertSame(failure, thrown.getCause());
+ assertEquals(0, failure.getSuppressed().length);
+ verify(delegate, never()).createContext(any(), any());
+ }
+
+ private static TProtocol createProtocol(SSLSocket socket) {
+ TProtocol protocol = mock(TProtocol.class);
+ TElasticFramedTransport framedTransport = mock(TElasticFramedTransport.class);
+ TSocket socketTransport = mock(TSocket.class);
+ when(protocol.getTransport()).thenReturn(framedTransport);
+ when(framedTransport.getSocket()).thenReturn(socketTransport);
+ when(socketTransport.getSocket()).thenReturn(socket);
+ return protocol;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 7dc7e38602b84..7da51d6e3c2f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -571,6 +571,11 @@
jetty-http
${jetty.version}