This repository was archived by the owner on Sep 29, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathtraffic_forwarder.py
More file actions
185 lines (157 loc) · 7.1 KB
/
Copy pathtraffic_forwarder.py
File metadata and controls
185 lines (157 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import socket
import sys
import threading
import time
import logging
import signal
import os
# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Global flag for graceful shutdown
shutdown_flag = threading.Event()
def signal_handler(sig, frame):
logging.info("Received shutdown signal")
shutdown_flag.set()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def forward(source, destination, connection_id, direction):
"""Forward data between sockets with proper cleanup"""
try:
source.settimeout(1.0) # 1 second timeout for checking shutdown
while not shutdown_flag.is_set():
try:
data = source.recv(1024)
if not data:
logging.info(f"Connection {connection_id}: End of data stream ({direction})")
break
destination.sendall(data)
except socket.timeout:
continue # Check shutdown flag
except OSError as e:
# Check if it's a bad file descriptor or transport endpoint error
if e.errno in (9, 107) or "Bad file descriptor" in str(e) or "Transport endpoint is not connected" in str(e):
# Socket was closed by the other thread, this is normal during shutdown
logging.debug(f"Connection {connection_id}: Socket closed by peer ({direction})")
else:
logging.error(f"Connection {connection_id}: Forwarding error ({direction}): {e}")
break
except Exception as e:
if not shutdown_flag.is_set():
logging.error(f"Connection {connection_id}: Forwarding error ({direction}): {e}")
break
except Exception as e:
logging.error(f"Connection {connection_id}: Fatal error ({direction}): {e}")
finally:
# Only shutdown our reading side and their writing side
try:
if direction == "client->server":
source.shutdown(socket.SHUT_RD)
destination.shutdown(socket.SHUT_WR)
else: # server->client
source.shutdown(socket.SHUT_RD)
destination.shutdown(socket.SHUT_WR)
except OSError:
# Socket might already be closed, that's OK
pass
logging.info(f"Connection {connection_id}: Completed ({direction})")
def handle_connection(client_socket, client_addr, remote_cid, remote_port, connection_id):
"""Handle a single connection with proper resource management"""
server_socket = None
threads = []
try:
# Connect to VSOCK
server_socket = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
server_socket.settimeout(30) # 30 second timeout for connection
server_socket.connect((remote_cid, remote_port))
logging.info(f"Connection {connection_id}: Connected to VSOCK {remote_cid}:{remote_port}")
# Create forwarding threads
outgoing_thread = threading.Thread(
target=forward,
args=(client_socket, server_socket, connection_id, "client->server"),
name=f"forward-{connection_id}-out"
)
incoming_thread = threading.Thread(
target=forward,
args=(server_socket, client_socket, connection_id, "server->client"),
name=f"forward-{connection_id}-in"
)
threads = [outgoing_thread, incoming_thread]
# Start threads
for thread in threads:
thread.start()
# Wait for threads to complete
for thread in threads:
thread.join()
except Exception as e:
logging.error(f"Connection {connection_id}: Failed to establish connection: {e}")
finally:
# Now that both forwarding threads are done, we can fully close the sockets
for sock in [client_socket, server_socket]:
if sock:
try:
# Both directions should already be shutdown by the forwarding threads
sock.close()
except OSError:
pass # Already closed
logging.info(f"Connection {connection_id}: Handler complete")
def server(local_ip, local_port, remote_cid, remote_port):
"""Main server with proper resource management and graceful shutdown"""
dock_socket = None
connection_counter = 0
active_connections = []
try:
dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dock_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
dock_socket.bind((local_ip, local_port))
dock_socket.listen(5)
dock_socket.settimeout(1.0) # Check for shutdown every second
logging.info(f"Listening on {local_ip}:{local_port}")
while not shutdown_flag.is_set():
try:
client_socket, client_addr = dock_socket.accept()
connection_counter += 1
connection_id = f"{connection_counter}"
logging.info(f"Connection {connection_id}: Accepted from {client_addr}")
# Handle connection in a separate thread
handler_thread = threading.Thread(
target=handle_connection,
args=(client_socket, client_addr, remote_cid, remote_port, connection_id),
name=f"handler-{connection_id}"
)
handler_thread.daemon = True # Allow main thread to exit
handler_thread.start()
active_connections.append(handler_thread)
# Clean up finished threads
active_connections = [t for t in active_connections if t.is_alive()]
except socket.timeout:
continue # Check shutdown flag
except Exception as e:
if not shutdown_flag.is_set():
logging.error(f"Server error: {e}")
except Exception as e:
logging.error(f"Failed to start server: {e}")
finally:
if dock_socket:
try:
dock_socket.close()
except:
pass
# Wait for active connections to finish
logging.info("Waiting for active connections to close...")
for thread in active_connections:
thread.join(timeout=5)
logging.info("Server shutdown complete")
def main(args):
if len(args) < 4:
logging.error("Usage: python traffic_forwarder.py <local_ip> <local_port> <remote_cid> <remote_port>")
sys.exit(1)
local_ip = str(args[0])
local_port = int(args[1])
remote_cid = int(args[2])
remote_port = int(args[3])
logging.info(f"Starting forwarder on {local_ip}:{local_port} to {remote_cid}:{remote_port}")
# Run server (will block until shutdown)
server(local_ip, local_port, remote_cid, remote_port)
logging.info("Traffic forwarder exiting")
if __name__ == '__main__':
main(sys.argv[1:])