Skip to content

Commit d555379

Browse files
Fix MITM using unique source ports for each server-side connection (finos#577)
* Fix MITM using unique source ports for each server-side connection Each new client SYN now increments the server-side source port so the server sees a distinct 4-tuple per client/reconnection, avoiding TCP state machine corruption when clients reconnect. The packet filter is updated to accept packets on the current server_conn.local_port in addition to mitm_port, so server responses to the unique port are not dropped. Closes finos#576 * Disable Python stdout buffering in embedded interpreter Python's stdout is block-buffered when connected to a pipe (e.g. ctest in CI). Forked child processes exit via std::exit() without calling Py_Finalize(), so Python's internal BufferedWriter is never flushed and all print() output is silently dropped. Use Py_UnbufferedStdioFlag on Python < 3.12 and PyConfig.buffered_stdio on Python >= 3.12 (where the flag is deprecated) to make stdout unbuffered before Py_Initialize(). * Re-enable UVYMQMitmTest/Reconnect on Windows The test was disabled in finos#576 due to TCP state machine corruption caused by the MITM always reusing the same source port for server-side connections. This is fixed by the unique-port counter introduced in this branch. * Consolidate MITM platform IP selection into getMitmIPs() helper Removes repeated #ifdef __linux__ / _WIN32 blocks from each MITM test by introducing a MitmIPs struct and getMitmIPs() in testing.h/cpp. * Assign static unique ports to MITM tests, remove randomPort() Replaces randomPort() calls with fixed ports 23575-23578 for the four MITM tests, matching the pattern of the existing static remote_ports (23571-23574). Removes the now-unused randomPort() utility. * Apply getMitmIPs() and static ports to uv_ymq MITM tests Same cleanup as done for test_ymq.cpp: replace repeated #ifdef blocks with getMitmIPs() and assign static unique mitm ports (23579-23582).
1 parent 8d7196f commit d555379

File tree

7 files changed

+65
-94
lines changed

7 files changed

+65
-94
lines changed

tests/cpp/uv_ymq/test_mitm.cpp

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,9 @@ TestResult reconnectClientMain(std::string address)
136136
// for more, see the python mitm files
137137
TEST_F(UVYMQMitmTest, Passthrough)
138138
{
139-
#ifdef __linux__
140-
auto mitm_ip = "192.0.2.4";
141-
auto remote_ip = "192.0.2.3";
142-
#else
143-
auto mitm_ip = "127.0.0.1";
144-
auto remote_ip = "127.0.0.1";
145-
#endif
146-
auto mitm_port = randomPort();
147-
auto remote_port = 23571;
139+
auto [mitm_ip, remote_ip] = getMitmIPs();
140+
auto mitm_port = 23579;
141+
auto remote_port = 23571;
148142

149143
// the Python program must be the first and only the first function passed to test()
150144
// we must also pass `true` as the third argument to ensure that Python is fully started
@@ -162,15 +156,9 @@ TEST_F(UVYMQMitmTest, Passthrough)
162156
// this is the same as the above, but both the client and server use raw sockets
163157
TEST_F(UVYMQMitmTest, PassthroughRaw)
164158
{
165-
#ifdef __linux__
166-
auto mitm_ip = "192.0.2.4";
167-
auto remote_ip = "192.0.2.3";
168-
#else
169-
auto mitm_ip = "127.0.0.1";
170-
auto remote_ip = "127.0.0.1";
171-
#endif
172-
auto mitm_port = randomPort();
173-
auto remote_port = 23574;
159+
auto [mitm_ip, remote_ip] = getMitmIPs();
160+
auto mitm_port = 23580;
161+
auto remote_port = 23574;
174162

175163
// the Python program must be the first and only the first function passed to test()
176164
// we must also pass `true` as the third argument to ensure that Python is fully started
@@ -185,22 +173,11 @@ TEST_F(UVYMQMitmTest, PassthroughRaw)
185173
}
186174

187175
// this test uses the mitm to test the reconnect logic of uv_ymq by sending RST packets
188-
#ifdef _WIN32
189-
// See https://github.com/finos/opengris-scaler/issues/576
190-
TEST_F(UVYMQMitmTest, DISABLED_Reconnect)
191-
#else
192176
TEST_F(UVYMQMitmTest, Reconnect)
193-
#endif
194177
{
195-
#ifdef __linux__
196-
auto mitm_ip = "192.0.2.4";
197-
auto remote_ip = "192.0.2.3";
198-
#else
199-
auto mitm_ip = "127.0.0.1";
200-
auto remote_ip = "127.0.0.1";
201-
#endif
202-
auto mitm_port = randomPort();
203-
auto remote_port = 23572;
178+
auto [mitm_ip, remote_ip] = getMitmIPs();
179+
auto mitm_port = 23581;
180+
auto remote_port = 23572;
204181

205182
auto result = test(
206183
30,
@@ -215,15 +192,9 @@ TEST_F(UVYMQMitmTest, Reconnect)
215192
// in this test, the mitm drops a random % of packets arriving from the client and server
216193
TEST_F(UVYMQMitmTest, RandomlyDropPackets)
217194
{
218-
#ifdef __linux__
219-
auto mitm_ip = "192.0.2.4";
220-
auto remote_ip = "192.0.2.3";
221-
#else
222-
auto mitm_ip = "127.0.0.1";
223-
auto remote_ip = "127.0.0.1";
224-
#endif
225-
auto mitm_port = randomPort();
226-
auto remote_port = 23573;
195+
auto [mitm_ip, remote_ip] = getMitmIPs();
196+
auto mitm_port = 23582;
197+
auto remote_port = 23573;
227198

228199
auto result = test(
229200
180,

tests/cpp/ymq/common/testing.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,25 @@ void ensurePythonInitialized()
7979
return;
8080

8181
ensurePythonHome();
82+
83+
// Disable Python's stdout buffering so that print() output is flushed
84+
// immediately when stdout is a pipe (e.g. under ctest in CI), rather than
85+
// being lost when the forked child exits without calling Py_Finalize().
86+
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 12
87+
// Py_UnbufferedStdioFlag is deprecated in Python 3.12+; use PyConfig instead.
88+
{
89+
PyConfig config;
90+
PyConfig_InitPythonConfig(&config);
91+
config.buffered_stdio = 0;
92+
PyStatus status = Py_InitializeFromConfig(&config);
93+
PyConfig_Clear(&config);
94+
if (PyStatus_Exception(status))
95+
throw std::runtime_error("failed to initialize Python interpreter");
96+
}
97+
#else
98+
Py_UnbufferedStdioFlag = 1;
8299
Py_Initialize();
100+
#endif
83101

84102
// add the cwd to the path
85103
{
@@ -161,6 +179,15 @@ TestResult runPython(const char* path, std::vector<std::optional<std::string>> a
161179
return TestResult::Failure;
162180
}
163181

182+
MitmIPs getMitmIPs()
183+
{
184+
#ifdef __linux__
185+
return {"192.0.2.4", "192.0.2.3"};
186+
#else
187+
return {"127.0.0.1", "127.0.0.1"};
188+
#endif
189+
}
190+
164191
TestResult runMitm(
165192
std::string testCase,
166193
std::string mitmIp,

tests/cpp/ymq/common/testing.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ int getListenerPid();
4646

4747
TestResult runPython(const char* path, std::vector<std::optional<std::string>> argv = {});
4848

49+
struct MitmIPs {
50+
const char* mitmIp;
51+
const char* remoteIp;
52+
};
53+
54+
// returns the platform-appropriate IP addresses for MITM tests
55+
MitmIPs getMitmIPs();
56+
4957
TestResult runMitm(
5058
std::string testCase,
5159
std::string mitmIp,

tests/cpp/ymq/common/utils.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "tests/cpp/ymq/common/utils.h"
22

33
#include <filesystem>
4-
#include <random>
54

65
// change the current working directory to the project root
76
// this is important for finding the python mitm script
@@ -18,10 +17,3 @@ void chdirToProjectRoot()
1817
}
1918
}
2019
}
21-
22-
unsigned short randomPort(unsigned short minPort, unsigned short maxPort)
23-
{
24-
static thread_local std::mt19937_64 rng(std::random_device {}());
25-
std::uniform_int_distribution<unsigned int> dist(minPort, maxPort);
26-
return static_cast<unsigned short>(dist(rng));
27-
}

tests/cpp/ymq/common/utils.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,3 @@ void raiseSocketError(const char* msg);
1212
// change the current working directory to the project root
1313
// this is important for finding the python mitm script
1414
void chdirToProjectRoot();
15-
16-
unsigned short randomPort(unsigned short minPort = 1024, unsigned short maxPort = 65535);

tests/cpp/ymq/py_mitm/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ def main(pid: int, mitm_ip: str, mitm_port: int, remote_ip: str, server_port: in
5454

5555
# the port that the mitm uses to connect to the server
5656
# we increment the port for each new connection to avoid collisions
57-
server_conn = TCPConnection(mitm_ip, mitm_port, remote_ip, server_port)
57+
next_server_port = mitm_port
58+
server_conn = TCPConnection(mitm_ip, next_server_port, remote_ip, server_port)
5859

5960
# tracks the state of each connection
6061
client_closed = False
@@ -66,7 +67,8 @@ def main(pid: int, mitm_ip: str, mitm_port: int, remote_ip: str, server_port: in
6667
if not pkt.haslayer(IP) or not pkt.haslayer(TCP):
6768
continue
6869

69-
if pkt.sport != mitm_port and pkt.dport != mitm_port:
70+
active_ports = {mitm_port, server_conn.local_port}
71+
if pkt.sport not in active_ports and pkt.dport not in active_ports:
7072
continue
7173

7274
ip = pkt[IP]
@@ -81,7 +83,8 @@ def main(pid: int, mitm_ip: str, mitm_port: int, remote_ip: str, server_port: in
8183
print(f"[*] Client {ip.src}:{tcp.sport} connecting to {ip.dst}:{tcp.dport}")
8284
client_conn = sender
8385

84-
server_conn = TCPConnection(mitm_ip, mitm_port, remote_ip, server_port)
86+
next_server_port += 1
87+
server_conn = TCPConnection(mitm_ip, next_server_port, remote_ip, server_port)
8588

8689
if tcp.flags.S and tcp.flags.A: # SYN-ACK from server
8790
if sender == server_conn:

tests/cpp/ymq/test_ymq.cpp

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -669,16 +669,9 @@ TEST_P(CcYmqTestSuiteParametrized, TestClientSendBigMessageToServer)
669669
// for more, see the python mitm files
670670
TEST(CcYmqTestSuite, TestMitmPassthrough)
671671
{
672-
#ifdef __linux__
673-
auto mitm_ip = "192.0.2.4";
674-
auto remote_ip = "192.0.2.3";
675-
#endif // __linux__
676-
#ifdef _WIN32
677-
auto mitm_ip = "127.0.0.1";
678-
auto remote_ip = "127.0.0.1";
679-
#endif // _WIN32
680-
auto mitm_port = randomPort();
681-
auto remote_port = 23571;
672+
auto [mitm_ip, remote_ip] = getMitmIPs();
673+
auto mitm_port = 23575;
674+
auto remote_port = 23571;
682675

683676
// the Python program must be the first and only the first function passed to test()
684677
// we must also pass `true` as the third argument to ensure that Python is fully started
@@ -696,16 +689,9 @@ TEST(CcYmqTestSuite, TestMitmPassthrough)
696689
// this is the same as the above, but both the client and server use raw sockets
697690
TEST(CcYmqTestSuite, TestMitmPassthroughRaw)
698691
{
699-
#ifdef __linux__
700-
auto mitm_ip = "192.0.2.4";
701-
auto remote_ip = "192.0.2.3";
702-
#endif // __linux__
703-
#ifdef _WIN32
704-
auto mitm_ip = "127.0.0.1";
705-
auto remote_ip = "127.0.0.1";
706-
#endif // _WIN32
707-
auto mitm_port = randomPort();
708-
auto remote_port = 23574;
692+
auto [mitm_ip, remote_ip] = getMitmIPs();
693+
auto mitm_port = 23576;
694+
auto remote_port = 23574;
709695

710696
// the Python program must be the first and only the first function passed to test()
711697
// we must also pass `true` as the third argument to ensure that Python is fully started
@@ -722,16 +708,9 @@ TEST(CcYmqTestSuite, TestMitmPassthroughRaw)
722708
// this test uses the mitm to test the reconnect logic of YMQ by sending RST packets
723709
TEST(CcYmqTestSuite, TestMitmReconnect)
724710
{
725-
#ifdef __linux__
726-
auto mitm_ip = "192.0.2.4";
727-
auto remote_ip = "192.0.2.3";
728-
#endif // __linux__
729-
#ifdef _WIN32
730-
auto mitm_ip = "127.0.0.1";
731-
auto remote_ip = "127.0.0.1";
732-
#endif // _WIN32
733-
auto mitm_port = randomPort();
734-
auto remote_port = 23572;
711+
auto [mitm_ip, remote_ip] = getMitmIPs();
712+
auto mitm_port = 23577;
713+
auto remote_port = 23572;
735714

736715
auto result = test(
737716
30,
@@ -746,16 +725,9 @@ TEST(CcYmqTestSuite, TestMitmReconnect)
746725
// in this test, the mitm drops a random % of packets arriving from the client and server
747726
TEST(CcYmqTestSuite, TestMitmRandomlyDropPackets)
748727
{
749-
#ifdef __linux__
750-
auto mitm_ip = "192.0.2.4";
751-
auto remote_ip = "192.0.2.3";
752-
#endif // __linux__
753-
#ifdef _WIN32
754-
auto mitm_ip = "127.0.0.1";
755-
auto remote_ip = "127.0.0.1";
756-
#endif // _WIN32
757-
auto mitm_port = randomPort();
758-
auto remote_port = 23573;
728+
auto [mitm_ip, remote_ip] = getMitmIPs();
729+
auto mitm_port = 23578;
730+
auto remote_port = 23573;
759731

760732
auto result = test(
761733
180,

0 commit comments

Comments
 (0)