Skip to content

Commit 401c930

Browse files
committed
fix traffic-masking
1 parent 028d063 commit 401c930

4 files changed

Lines changed: 97 additions & 39 deletions

File tree

traffic-masking/.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.3
1+
1.0.4

traffic-masking/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to the Traffic Masking System will be documented in this fil
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.4] - 2025-02-12
9+
10+
### Fixed
11+
- Client no longer falsely reports "Reconnected successfully" when server is unreachable
12+
- Connection status now reflects actual data flow, not just successful UDP send
13+
- Exponential backoff now works correctly (was resetting every cycle due to false success)
14+
15+
### Changed
16+
- Reconnection logic now waits for actual server response before confirming connection
17+
- Keepalive is only sent when connected; on timeout the client enters reconnect loop first
18+
- All Russian comments in source code translated to English
19+
20+
### Improved
21+
- Reconnection test now validates no false reconnection reports during server downtime
22+
- Reconnection test verifies client shows `disconnected` status while server is down
23+
- Reconnection test requires `Reconnected successfully` message only after real server recovery
24+
825
## [1.0.3] - 2025-02-12
926

1027
### Fixed

traffic-masking/test_traffic_masking.py

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -604,15 +604,40 @@ def start_server():
604604
with open(client_log_path, "r") as f:
605605
loss_log = f.read()
606606

607-
if (
608-
"disconnected" in loss_log
609-
or "Connection lost" in loss_log
610-
or "Reconnecting" in loss_log
611-
):
607+
if "Connection lost" in loss_log:
612608
print("✓ Phase 2: Client detected connection loss")
613609
else:
614610
print("⚠ Phase 2: Client may not have detected loss (checking Phase 3)")
615611

612+
# Verify client does NOT falsely report successful reconnection while server is down
613+
# Split log into lines after "Connection lost" to check behavior during downtime
614+
loss_idx = loss_log.find("Connection lost")
615+
if loss_idx >= 0:
616+
downtime_log = loss_log[loss_idx:]
617+
false_reconnects = downtime_log.count("Reconnected successfully")
618+
if false_reconnects > 0:
619+
print(
620+
f"✗ Phase 2: Client falsely reported 'Reconnected successfully' {false_reconnects} time(s) while server was down"
621+
)
622+
test_results["transmission"]["reconnect"] = False
623+
return False
624+
else:
625+
print("✓ Phase 2: No false reconnection reports during server downtime")
626+
627+
# Verify client shows disconnected status (not connected) during downtime
628+
downtime_stats = [l for l in downtime_log.splitlines() if "[STATS]" in l]
629+
false_connected = [l for l in downtime_stats if "Status: connected" in l]
630+
if false_connected:
631+
print(
632+
f"✗ Phase 2: Client reported 'connected' status {len(false_connected)} time(s) while server was down"
633+
)
634+
test_results["transmission"]["reconnect"] = False
635+
return False
636+
else:
637+
print(
638+
"✓ Phase 2: Client correctly reported disconnected status during downtime"
639+
)
640+
616641
# Phase 3: Restart server, verify client reconnects and resumes traffic
617642
print("[RECONN] Phase 3: Restarting server...")
618643
server_proc = start_server()
@@ -644,33 +669,36 @@ def start_server():
644669
test_results["transmission"]["reconnect"] = False
645670
return False
646671

647-
# Check if the last few stats lines show data reception
672+
# Verify "Reconnected successfully" appears after server restart
673+
# (should only appear now, not during Phase 2 downtime)
674+
has_reconnected_msg = "Reconnected successfully" in full_log
675+
if has_reconnected_msg:
676+
print("✓ Phase 3: Client reported successful reconnection")
677+
else:
678+
print("✗ Phase 3: Client never reported 'Reconnected successfully'")
679+
test_results["transmission"]["reconnect"] = False
680+
return False
681+
682+
# Check the last few stats lines show connected status and data flow
648683
last_stats = stats_lines[-3:]
684+
has_connected_status = any("Status: connected" in l for l in last_stats)
649685
recovered = False
650686
for line in last_stats:
651687
rx_match = re.search(r"Rx:\s*([0-9.]+)\s*Mbps", line)
652688
if rx_match and float(rx_match.group(1)) > 0.1:
653689
recovered = True
654690
break
655691

656-
# Also check for connected status in recent lines
657-
has_connected_status = any(
658-
"connected" in l and "disconnected" not in l
659-
for l in full_log.splitlines()[-10:]
660-
if "[STATS]" in l
661-
)
662-
663-
# Check for reconnection log messages
664-
has_reconnect_msg = "Reconnect" in full_log or "Registration sent" in full_log
665-
666-
if recovered or has_connected_status:
667-
print("✓ Phase 3: Client reconnected and resumed receiving data")
692+
if has_connected_status and recovered:
693+
print("✓ Phase 3: Client connected and receiving data after recovery")
668694
test_results["transmission"]["reconnect"] = True
669695
success = True
670-
elif has_reconnect_msg:
671-
print(
672-
"✓ Phase 3: Client attempted reconnection (traffic may still be ramping up)"
673-
)
696+
elif has_connected_status:
697+
print("✓ Phase 3: Client connected (traffic still ramping up)")
698+
test_results["transmission"]["reconnect"] = True
699+
success = True
700+
elif recovered:
701+
print("✓ Phase 3: Client receiving data after recovery")
674702
test_results["transmission"]["reconnect"] = True
675703
success = True
676704
else:

traffic-masking/traffic_masking_client.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,40 +88,50 @@ def _create_socket(self):
8888
self.socket = init_udp_socket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
8989
self.socket.settimeout(2.0)
9090

91-
def _register(self):
92-
"""Send registration packet to the server"""
91+
def _send_registration(self):
92+
"""Send registration packet to the server (UDP: no delivery guarantee)"""
9393
try:
9494
self.socket.sendto(b"INIT_CLIENT", self.server_addr)
95-
self.connected = True
96-
self.last_received = time.time()
9795
print(
9896
f"[*] Registration sent to {self.server_host}:{self.server_port}",
9997
flush=True,
10098
)
99+
return True
101100
except Exception as e:
102101
print(f"[!] Registration failed: {e}", flush=True)
103-
self.connected = False
102+
return False
103+
104+
def _wait_for_server(self, timeout=5.0):
105+
"""Wait for actual data from the server to confirm connection"""
106+
deadline = time.time() + timeout
107+
while time.time() < deadline and self.running:
108+
if self.connected:
109+
return True
110+
time.sleep(0.2)
111+
return self.connected
104112

105113
def _reconnect(self):
106114
"""Reconnect to the server with exponential backoff"""
107115
delay = self.RECONNECT_DELAY_MIN
108116
while self.running:
109117
print(
110-
f"[*] Reconnecting in {delay:.1f}s...",
118+
f"[*] Attempting reconnect in {delay:.1f}s...",
111119
flush=True,
112120
)
113121
time.sleep(delay)
114122
if not self.running:
115123
break
116124
try:
117125
self._create_socket()
118-
self._register()
119-
if self.connected:
126+
self._send_registration()
127+
# Wait for actual server response to confirm connection
128+
if self._wait_for_server(timeout=delay + 2.0):
120129
print("[*] Reconnected successfully", flush=True)
121-
# Reset rate tracking for fresh start
122130
self.received_rate = 0
123131
self.rate_window.clear()
124132
return
133+
else:
134+
print("[!] No response from server", flush=True)
125135
except Exception as e:
126136
print(f"[!] Reconnect failed: {e}", flush=True)
127137
delay = min(delay * 2, self.RECONNECT_DELAY_MAX)
@@ -144,7 +154,8 @@ def connect(self):
144154
)
145155

146156
# Send initial registration packet
147-
self._register()
157+
self._send_registration()
158+
self.last_received = time.time() # Grace period for initial connection
148159

149160
# Start threads
150161
threading.Thread(target=self.receive_loop, daemon=True).start()
@@ -247,12 +258,6 @@ def keepalive_loop(self):
247258
if not self.running:
248259
break
249260

250-
# Send keepalive to stay registered on the server
251-
try:
252-
self.socket.sendto(b"KEEPALIVE", self.server_addr)
253-
except Exception:
254-
pass
255-
256261
# Check if we've lost the connection
257262
if (
258263
self.last_received > 0
@@ -265,6 +270,14 @@ def keepalive_loop(self):
265270
self.connected = False
266271
self.received_rate = 0
267272
self._reconnect()
273+
# After _reconnect returns (success), resume keepalive loop
274+
continue
275+
276+
# Send keepalive to stay registered on the server
277+
try:
278+
self.socket.sendto(b"KEEPALIVE", self.server_addr)
279+
except Exception:
280+
pass
268281

269282
def send_loop(self):
270283
"""Generate uplink traffic"""

0 commit comments

Comments
 (0)