Skip to content

Commit 429bf57

Browse files
authored
Fix unsolicited AirGap responses on idle timeout (#18502) (#18506)
Backport #18502 to dev/1.3.
1 parent d2abcff commit 429bf57

2 files changed

Lines changed: 117 additions & 1 deletion

File tree

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/receiver/protocol/airgap/IoTDBAirGapReceiver.java‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939
import org.slf4j.LoggerFactory;
4040

4141
import java.io.BufferedInputStream;
42+
import java.io.FilterInputStream;
4243
import java.io.IOException;
4344
import java.io.InputStream;
4445
import java.io.OutputStream;
4546
import java.net.Socket;
47+
import java.net.SocketTimeoutException;
4648
import java.nio.ByteBuffer;
4749
import java.util.Arrays;
4850
import java.util.zip.CRC32;
@@ -101,7 +103,9 @@ public void runMayThrow() throws Throwable {
101103
}
102104

103105
private void receive() throws IOException {
104-
final InputStream inputStream = new BufferedInputStream(socket.getInputStream());
106+
final ReadProgressInputStream readProgressInputStream =
107+
new ReadProgressInputStream(socket.getInputStream());
108+
final InputStream inputStream = new BufferedInputStream(readProgressInputStream);
105109

106110
try {
107111
final byte[] data = readData(inputStream);
@@ -134,6 +138,18 @@ private void receive() throws IOException {
134138
.setType(ReadWriteIOUtils.readShort(byteBuffer))
135139
.setBody(byteBuffer.slice());
136140
handleReq(req, System.currentTimeMillis());
141+
} catch (final SocketTimeoutException e) {
142+
// It is normal for an air gap sender to remain idle. Only close the connection when the
143+
// timeout occurs after a request has started, because the stream can no longer be decoded
144+
// reliably in that case. Do not send FAIL without receiving a complete request.
145+
if (readProgressInputStream.hasReadAnyByte()) {
146+
LOGGER.warn(
147+
"Pipe air gap receiver {}: Exception during handling receiving. Socket: {}",
148+
receiverId,
149+
socket,
150+
e);
151+
socket.close();
152+
}
137153
} catch (final PipeConnectionException e) {
138154
LOGGER.info(
139155
"Pipe air gap receiver {}: Socket {} closed when listening to data. Because: {}",
@@ -327,4 +343,35 @@ private void skipTillEnough(final InputStream inputStream, final long length)
327343
currentSkippedBytes += skippedBytes;
328344
}
329345
}
346+
347+
private static class ReadProgressInputStream extends FilterInputStream {
348+
349+
private boolean hasReadAnyByte;
350+
351+
private ReadProgressInputStream(final InputStream inputStream) {
352+
super(inputStream);
353+
}
354+
355+
@Override
356+
public int read() throws IOException {
357+
final int result = super.read();
358+
if (result >= 0) {
359+
hasReadAnyByte = true;
360+
}
361+
return result;
362+
}
363+
364+
@Override
365+
public int read(final byte[] buffer, final int offset, final int length) throws IOException {
366+
final int result = super.read(buffer, offset, length);
367+
if (result > 0) {
368+
hasReadAnyByte = true;
369+
}
370+
return result;
371+
}
372+
373+
private boolean hasReadAnyByte() {
374+
return hasReadAnyByte;
375+
}
376+
}
330377
}

‎iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/receiver/protocol/airgap/IoTDBAirGapReceiverTest.java‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.lang.reflect.Field;
4545
import java.lang.reflect.Method;
4646
import java.net.Socket;
47+
import java.net.SocketTimeoutException;
4748
import java.nio.ByteBuffer;
4849

4950
public class IoTDBAirGapReceiverTest {
@@ -121,6 +122,31 @@ public void testTemporaryUnavailableRetryTimeoutReturnsFail() throws Exception {
121122
}
122123
}
123124

125+
@Test
126+
public void testIdleReadTimeoutDoesNotRespondOrCloseSocket() throws Exception {
127+
final RecordingSocket socket = new RecordingSocket(new TimeoutInputStream(new byte[0]));
128+
invokeReceive(new IoTDBAirGapReceiver(socket, 4L));
129+
130+
Assert.assertArrayEquals(new byte[0], socket.getWrittenBytes());
131+
Assert.assertFalse(socket.isClosed());
132+
}
133+
134+
@Test
135+
public void testPartialRequestReadTimeoutClosesSocketWithoutResponse() throws Exception {
136+
final RecordingSocket socket =
137+
new RecordingSocket(new TimeoutInputStream(new byte[] {(byte) 0xFF}));
138+
invokeReceive(new IoTDBAirGapReceiver(socket, 5L));
139+
140+
Assert.assertArrayEquals(new byte[0], socket.getWrittenBytes());
141+
Assert.assertTrue(socket.isClosed());
142+
}
143+
144+
private static void invokeReceive(final IoTDBAirGapReceiver receiver) throws Exception {
145+
final Method receive = IoTDBAirGapReceiver.class.getDeclaredMethod("receive");
146+
receive.setAccessible(true);
147+
receive.invoke(receiver);
148+
}
149+
124150
private static void setField(final Object target, final String fieldName, final Object value)
125151
throws Exception {
126152
final Field field = IoTDBAirGapReceiver.class.getDeclaredField(fieldName);
@@ -130,8 +156,22 @@ private static void setField(final Object target, final String fieldName, final
130156

131157
private static class RecordingSocket extends Socket {
132158

159+
private final InputStream inputStream;
133160
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
134161

162+
private RecordingSocket() {
163+
this(new ByteArrayInputStream(new byte[0]));
164+
}
165+
166+
private RecordingSocket(final InputStream inputStream) {
167+
this.inputStream = inputStream;
168+
}
169+
170+
@Override
171+
public InputStream getInputStream() {
172+
return inputStream;
173+
}
174+
135175
@Override
136176
public OutputStream getOutputStream() {
137177
return outputStream;
@@ -142,6 +182,35 @@ byte[] getWrittenBytes() {
142182
}
143183
}
144184

185+
private static class TimeoutInputStream extends InputStream {
186+
187+
private final byte[] bytesBeforeTimeout;
188+
private int position;
189+
190+
private TimeoutInputStream(final byte[] bytesBeforeTimeout) {
191+
this.bytesBeforeTimeout = bytesBeforeTimeout;
192+
}
193+
194+
@Override
195+
public int read() throws IOException {
196+
if (position >= bytesBeforeTimeout.length) {
197+
throw new SocketTimeoutException("Test timeout");
198+
}
199+
return bytesBeforeTimeout[position++] & 0xFF;
200+
}
201+
202+
@Override
203+
public int read(final byte[] buffer, final int offset, final int length) throws IOException {
204+
if (position >= bytesBeforeTimeout.length) {
205+
throw new SocketTimeoutException("Test timeout");
206+
}
207+
final int bytesToRead = Math.min(length, bytesBeforeTimeout.length - position);
208+
System.arraycopy(bytesBeforeTimeout, position, buffer, offset, bytesToRead);
209+
position += bytesToRead;
210+
return bytesToRead;
211+
}
212+
}
213+
145214
private static class StubIoTDBDataNodeReceiverAgent extends IoTDBDataNodeReceiverAgent {
146215

147216
void setStubReceiver(final IoTDBReceiver receiver) {

0 commit comments

Comments
 (0)