forked from ciciplusplus/CaORE
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.py
More file actions
197 lines (171 loc) · 17.6 KB
/
proxy.py
File metadata and controls
197 lines (171 loc) · 17.6 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
186
187
188
189
190
191
192
193
194
195
196
197
import socket
import threading
import argparse
import struct
import sys
def forward_data(source_sock, dest_sock, direction_name):
"""
Forwards data from source_sock to dest_sock.
Expects 2 LE bytes for length, then the message.
Prints length and message before forwarding.
"""
try:
while True:
# Read 2 Little Endian bytes for length
len_bytes = source_sock.recv(2)
if not len_bytes:
print(f"[{direction_name}] Connection closed by source (no length bytes).")
break
if len(len_bytes) < 2:
print(f"[{direction_name}] Connection closed prematurely (incomplete length bytes).")
break
msg_len = struct.unpack('<H', len_bytes)[0] # '<H' is little-endian unsigned short
message = b''
if msg_len > 0:
bytes_to_read = msg_len - 2
while bytes_to_read > 0:
chunk = source_sock.recv(bytes_to_read)
if not chunk:
print(f"[{direction_name}] Connection closed prematurely while reading message body.")
# This is an error state, as we expected more data.
# Exit this forwarding direction.
return
message += chunk
bytes_to_read -= len(chunk)
print(f"[{direction_name}] Length: {msg_len}, Message: {message!r}")
if b't-o-k-e-n' in message:
x = bytes([76, 0, 17, 0, 1, 0, 49, 1, 0, 49, 69, 0, 0, 0, 40, 0, 100, 99, 51, 97, 101, 99, 55, 101, 53, 51, 55, 49, 52, 50, 52, 98, 56, 100, 50, 97, 97, 50, 97, 49, 102, 57, 55, 57, 97, 57, 53, 99, 53, 52, 102, 55, 100, 53, 98, 53, 1, 0, 49, 6, 0, 49, 46, 48, 46, 51, 108, 1, 0, 0, 0, 0, 0, 0, 0, 0])
dest_sock.sendall(x)
continue
# if msg_len == 56 and message == bytes([17, 0, 3, 0, 97, 98, 99, 9, 0, 116, 45, 111, 45, 107, 45, 101, 45, 110, 65, 0, 0, 0, 14, 0, 116, 111, 117, 99, 104, 72, 76, 69, 100, 101, 118, 105, 99, 101, 1, 0, 49, 6, 0, 49, 46, 48, 46, 49, 106, 0, 2, 0, 0, 0]):
# x = bytes([76, 0, 17, 0, 1, 0, 49, 1, 0, 49, 69, 0, 0, 0, 40, 0, 100, 99, 51, 97, 101, 99, 55, 101, 53, 51, 55, 49, 52, 50, 52, 98, 56, 100, 50, 97, 97, 50, 97, 49, 102, 57, 55, 57, 97, 57, 53, 99, 53, 52, 102, 55, 100, 53, 98, 53, 1, 0, 49, 6, 0, 49, 46, 48, 46, 51, 108, 1, 0, 0, 0, 0, 0, 0, 0, 0])
# dest_sock.sendall(x)
# continue
# if msg_len == 4504:
# y = b'\x02\x01\x1d\x00\x00\x00\n\x02\x00\x00\x01\x00\x00\x00\x01\x00\x01\x00\x02\x00\x00\x00\xc9\xb0@@\x00\x00\xe0@\xd0\x15gB\x80B\xa4<\x00\x00\x00\x00\xd3\xf2\x7f?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x98\x06\x00\x00\xab\x00\x00\x00\xab\x00\x00\x00\x02\x00\x00\x00\x14\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0@\x00\x00\x00\x00\x00\x00@@\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00ADC\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x0b\x00\x00\xbf\x0b\x00\x00\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00O\x04\x00\x00\x00\x00\x00\x00\xbd\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x0b\x00\x00\xbf\x0b\x00\x00\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00O\x04\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xbd\x04\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdc\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x80\x12\x00\x00\x00\x0f\x00\x00\x00\x10\x00\x00\x00\x0b\x00\x00\x00+\x00\x00\x80\x07\x00\x00\x00\x0b\x00\x00\x00\xdc\x05\x00\x00\x07\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\xdb\xb6\xad?\x00\x00\x00\x0033s?\x00\x00\x00\x00\x1f\x85k=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x0033s?\x06\x00\x00\x00\xb7m\xdb>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0033s?uL\xf0:\xb2\xc9\xa6=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x8fB=\xcd\xccL=\xcd\xccL=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x18\x00\x00\x00\x08\x9e\x884\x05\x04\x00\x00\x00\x04\x00\x00\x00\xda\x02\x00\x00\x00\x00\x00\x00\xfe\x0b\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x0b\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\xda\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\x884\x05\x04\x00\x00\x00\x05\x00\x00\x00\x93\x02\x00\x00\x00\x00\x00\x00\xbf\x0b\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbf\x0b\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x93\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9f\x884\x05\x04\x00\x00\x00\x06\x00\x00\x00\xdb\x02\x00\x00\x00\x00\x00\x00\xff\x0b\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0b\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\xdb\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x884\x05\x04\x00\x00\x00\r\x00\x00\x00\xe3\x01\x00\x00\x00\x00\x00\x00O\x04\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00O\x04\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xe3\x01\x00\x00[\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00f\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# dest_sock.sendall(y)
# continue
# Forward length and message
dest_sock.sendall(len_bytes)
dest_sock.sendall(message)
except ConnectionResetError:
print(f"[{direction_name}] Connection reset by peer.")
except BrokenPipeError:
print(f"[{direction_name}] Broken pipe.")
except socket.timeout:
print(f"[{direction_name}] Socket timeout.")
except Exception as e:
print(f"[{direction_name}] Error during forwarding: {e}", file=sys.stderr)
finally:
# This function doesn't own the sockets, so it shouldn't close them.
# The caller (handle_client) is responsible for closing.
print(f"[{direction_name}] Forwarding loop terminated.")
def handle_client(client_sock, client_addr, target_host, target_port):
"""
Handles a single client connection:
1. Connects to the target server.
2. Starts two threads for bi-directional forwarding.
3. Waits for threads to complete and closes sockets.
"""
print(f"Accepted connection from {client_addr[0]}:{client_addr[1]}")
target_sock = None
try:
target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Set a timeout for connecting to the target
target_sock.settimeout(10.0)
target_sock.connect((target_host, target_port))
target_sock.settimeout(None) # Disable timeout for data transfer
client_sock.settimeout(None) # Disable timeout for data transfer
print(f"Connected to target {target_host}:{target_port} for client {client_addr[0]}:{client_addr[1]}")
# Create threads for bi-directional forwarding
# Naming them helps in debugging
client_to_target_thread_name = f"{client_addr[0]}:{client_addr[1]} -> Target"
target_to_client_thread_name = f"Target -> {client_addr[0]}:{client_addr[1]}"
client_to_target_thread = threading.Thread(
target=forward_data,
args=(client_sock, target_sock, client_to_target_thread_name),
name=client_to_target_thread_name
)
target_to_client_thread = threading.Thread(
target=forward_data,
args=(target_sock, client_sock, target_to_client_thread_name),
name=target_to_client_thread_name
)
client_to_target_thread.start()
target_to_client_thread.start()
# Wait for both forwarding threads to complete.
# When one side of a TCP connection closes, the other side's recv()
# will eventually return 0 bytes, causing its forward_data loop to exit.
client_to_target_thread.join()
target_to_client_thread.join()
except socket.timeout:
print(f"Timeout connecting to target {target_host}:{target_port} for client {client_addr[0]}:{client_addr[1]}", file=sys.stderr)
except socket.error as e:
print(f"Socket error with target {target_host}:{target_port} for client {client_addr[0]}:{client_addr[1]}: {e}", file=sys.stderr)
except Exception as e:
print(f"Error in handle_client for {client_addr[0]}:{client_addr[1]}: {e}", file=sys.stderr)
finally:
print(f"Closing connections for {client_addr[0]}:{client_addr[1]}")
if client_sock:
try:
client_sock.shutdown(socket.SHUT_RDWR)
except socket.error:
pass # Ignore errors if already closed
client_sock.close()
if target_sock:
try:
target_sock.shutdown(socket.SHUT_RDWR)
except socket.error:
pass # Ignore errors if already closed
target_sock.close()
print(f"Connections closed for {client_addr[0]}:{client_addr[1]}")
def main():
parser = argparse.ArgumentParser(
description="Python Bi-directional TCP Proxy Server with Length-Prefixed Messages."
)
parser.add_argument("--source_host", help="Source host IP to listen on (e.g., 0.0.0.0 or localhost)")
parser.add_argument("--source_port", type=int, help="Source port to listen on (e.g., 8888)")
parser.add_argument("--target_host", help="Target host IP to redirect to (e.g., example.com or IP)")
parser.add_argument("--target_port", type=int, help="Target port to redirect to (e.g., 80)")
args = parser.parse_args()
server_sock = None
try:
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Allow reusing the address quickly after server restart
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind((args.source_host, args.source_port))
server_sock.listen(5) # Listen for up to 5 queued connections
print(f"Proxy server listening on {args.source_host}:{args.source_port}")
print(f"Redirecting traffic to {args.target_host}:{args.target_port}")
active_threads = []
while True:
client_sock, client_addr = server_sock.accept()
handler_thread = threading.Thread(
target=handle_client,
args=(client_sock, client_addr, args.target_host, args.target_port)
# Not setting daemon=True, so main thread waits for these on clean exit.
# For abrupt Ctrl+C, OS will handle it.
)
handler_thread.start()
active_threads.append(handler_thread)
# Optional: Clean up finished threads from the list to prevent it from growing indefinitely
active_threads = [t for t in active_threads if t.is_alive()]
except KeyboardInterrupt:
print("\nShutting down server (Ctrl+C pressed)...")
except Exception as e:
print(f"Server error: {e}", file=sys.stderr)
finally:
if server_sock:
print("Closing server socket.")
server_sock.close()
# Wait for all active client handler threads to complete (optional, for graceful shutdown)
# This part is tricky with KeyboardInterrupt, as threads might be stuck in blocking calls.
# For simplicity, we're relying on the OS to clean up threads if KeyboardInterrupt occurs.
# If you need truly graceful shutdown of active connections, more complex signaling is needed.
# print("Waiting for active connections to close...")
# for t in active_threads:
# if t.is_alive():
# t.join(timeout=5.0) # Give some time for threads to finish
print("Server shut down.")
if __name__ == "__main__":
main()