conson-xp 1.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conson_xp-1.18.0.dist-info/METADATA +412 -0
- conson_xp-1.18.0.dist-info/RECORD +176 -0
- conson_xp-1.18.0.dist-info/WHEEL +4 -0
- conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
- conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
- xp/__init__.py +9 -0
- xp/cli/__init__.py +5 -0
- xp/cli/__main__.py +6 -0
- xp/cli/commands/__init__.py +153 -0
- xp/cli/commands/conbus/__init__.py +25 -0
- xp/cli/commands/conbus/conbus.py +128 -0
- xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
- xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
- xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
- xp/cli/commands/conbus/conbus_config_commands.py +29 -0
- xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
- xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
- xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
- xp/cli/commands/conbus/conbus_event_commands.py +81 -0
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
- xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
- xp/cli/commands/conbus/conbus_output_commands.py +163 -0
- xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
- xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
- xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
- xp/cli/commands/file_commands.py +186 -0
- xp/cli/commands/homekit/__init__.py +3 -0
- xp/cli/commands/homekit/homekit.py +118 -0
- xp/cli/commands/homekit/homekit_start_commands.py +43 -0
- xp/cli/commands/module_commands.py +187 -0
- xp/cli/commands/reverse_proxy_commands.py +178 -0
- xp/cli/commands/server/__init__.py +3 -0
- xp/cli/commands/server/server_commands.py +135 -0
- xp/cli/commands/telegram/__init__.py +5 -0
- xp/cli/commands/telegram/telegram.py +41 -0
- xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
- xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
- xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
- xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
- xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
- xp/cli/commands/telegram/telegram_version_commands.py +52 -0
- xp/cli/main.py +87 -0
- xp/cli/utils/__init__.py +1 -0
- xp/cli/utils/click_tree.py +57 -0
- xp/cli/utils/datapoint_type_choice.py +57 -0
- xp/cli/utils/decorators.py +351 -0
- xp/cli/utils/error_handlers.py +201 -0
- xp/cli/utils/formatters.py +312 -0
- xp/cli/utils/module_type_choice.py +56 -0
- xp/cli/utils/serial_number_type.py +52 -0
- xp/cli/utils/system_function_choice.py +57 -0
- xp/cli/utils/xp_module_type.py +53 -0
- xp/connection/__init__.py +13 -0
- xp/connection/exceptions.py +22 -0
- xp/models/__init__.py +36 -0
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +43 -0
- xp/models/actiontable/msactiontable_xp20.py +53 -0
- xp/models/actiontable/msactiontable_xp24.py +58 -0
- xp/models/actiontable/msactiontable_xp33.py +65 -0
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +87 -0
- xp/models/conbus/conbus_autoreport.py +67 -0
- xp/models/conbus/conbus_blink.py +80 -0
- xp/models/conbus/conbus_client_config.py +55 -0
- xp/models/conbus/conbus_connection_status.py +40 -0
- xp/models/conbus/conbus_custom.py +58 -0
- xp/models/conbus/conbus_datapoint.py +89 -0
- xp/models/conbus/conbus_discover.py +64 -0
- xp/models/conbus/conbus_event_raw.py +47 -0
- xp/models/conbus/conbus_lightlevel.py +52 -0
- xp/models/conbus/conbus_linknumber.py +54 -0
- xp/models/conbus/conbus_output.py +57 -0
- xp/models/conbus/conbus_raw.py +45 -0
- xp/models/conbus/conbus_receive.py +42 -0
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +35 -0
- xp/models/homekit/homekit_config.py +106 -0
- xp/models/homekit/homekit_conson_config.py +86 -0
- xp/models/log_entry.py +130 -0
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +312 -0
- xp/models/response.py +42 -0
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +31 -0
- xp/models/telegram/datapoint_type.py +82 -0
- xp/models/telegram/event_telegram.py +140 -0
- xp/models/telegram/event_type.py +15 -0
- xp/models/telegram/input_action_type.py +69 -0
- xp/models/telegram/input_type.py +17 -0
- xp/models/telegram/module_type.py +188 -0
- xp/models/telegram/module_type_code.py +205 -0
- xp/models/telegram/output_telegram.py +103 -0
- xp/models/telegram/reply_telegram.py +297 -0
- xp/models/telegram/system_function.py +116 -0
- xp/models/telegram/system_telegram.py +94 -0
- xp/models/telegram/telegram.py +28 -0
- xp/models/telegram/telegram_type.py +19 -0
- xp/models/telegram/timeparam_type.py +51 -0
- xp/models/write_config_type.py +33 -0
- xp/services/__init__.py +26 -0
- xp/services/actiontable/__init__.py +1 -0
- xp/services/actiontable/actiontable_serializer.py +273 -0
- xp/services/actiontable/msactiontable_serializer.py +7 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
- xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
- xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
- xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
- xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
- xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
- xp/services/conbus/actiontable/msactiontable_service.py +232 -0
- xp/services/conbus/conbus_blink_all_service.py +181 -0
- xp/services/conbus/conbus_blink_service.py +158 -0
- xp/services/conbus/conbus_custom_service.py +156 -0
- xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
- xp/services/conbus/conbus_datapoint_service.py +170 -0
- xp/services/conbus/conbus_discover_service.py +312 -0
- xp/services/conbus/conbus_event_raw_service.py +181 -0
- xp/services/conbus/conbus_output_service.py +194 -0
- xp/services/conbus/conbus_raw_service.py +122 -0
- xp/services/conbus/conbus_receive_service.py +115 -0
- xp/services/conbus/conbus_scan_service.py +150 -0
- xp/services/conbus/write_config_service.py +194 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +307 -0
- xp/services/homekit/homekit_conbus_service.py +93 -0
- xp/services/homekit/homekit_config_validator.py +310 -0
- xp/services/homekit/homekit_conson_validator.py +121 -0
- xp/services/homekit/homekit_dimminglight.py +182 -0
- xp/services/homekit/homekit_dimminglight_service.py +148 -0
- xp/services/homekit/homekit_hap_service.py +342 -0
- xp/services/homekit/homekit_lightbulb.py +120 -0
- xp/services/homekit/homekit_lightbulb_service.py +86 -0
- xp/services/homekit/homekit_module_service.py +56 -0
- xp/services/homekit/homekit_outlet.py +168 -0
- xp/services/homekit/homekit_outlet_service.py +121 -0
- xp/services/homekit/homekit_service.py +359 -0
- xp/services/log_file_service.py +309 -0
- xp/services/module_type_service.py +257 -0
- xp/services/protocol/__init__.py +21 -0
- xp/services/protocol/conbus_event_protocol.py +360 -0
- xp/services/protocol/conbus_protocol.py +318 -0
- xp/services/protocol/protocol_factory.py +78 -0
- xp/services/protocol/telegram_protocol.py +264 -0
- xp/services/reverse_proxy_service.py +435 -0
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +366 -0
- xp/services/server/cp20_server_service.py +65 -0
- xp/services/server/device_service_factory.py +94 -0
- xp/services/server/server_service.py +428 -0
- xp/services/server/xp130_server_service.py +67 -0
- xp/services/server/xp20_server_service.py +92 -0
- xp/services/server/xp230_server_service.py +58 -0
- xp/services/server/xp24_server_service.py +245 -0
- xp/services/server/xp33_server_service.py +535 -0
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +138 -0
- xp/services/telegram/telegram_checksum_service.py +149 -0
- xp/services/telegram/telegram_datapoint_service.py +82 -0
- xp/services/telegram/telegram_discover_service.py +277 -0
- xp/services/telegram/telegram_link_number_service.py +216 -0
- xp/services/telegram/telegram_output_service.py +322 -0
- xp/services/telegram/telegram_service.py +380 -0
- xp/services/telegram/telegram_version_service.py +288 -0
- xp/utils/__init__.py +12 -0
- xp/utils/checksum.py +61 -0
- xp/utils/dependencies.py +531 -0
- xp/utils/event_helper.py +31 -0
- xp/utils/serialization.py +205 -0
- xp/utils/time_utils.py +134 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""Conbus Reverse Proxy Service for TCP relay with telegram monitoring.
|
|
2
|
+
|
|
3
|
+
This service implements a TCP reverse proxy that listens on port 10001 and forwards
|
|
4
|
+
all telegrams to the configured Conbus server while printing bidirectional traffic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import socket
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Dict, Optional
|
|
13
|
+
|
|
14
|
+
from xp.models import ConbusClientConfig
|
|
15
|
+
from xp.models.response import Response
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ReverseProxyError(Exception):
|
|
19
|
+
"""Raised when Conbus reverse proxy operations fail."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ReverseProxyService:
|
|
25
|
+
"""
|
|
26
|
+
TCP reverse proxy for Conbus communications.
|
|
27
|
+
|
|
28
|
+
Accepts client connections on port 10001 and forwards all telegrams
|
|
29
|
+
to the target server configured in cli.yml. Monitors and prints all
|
|
30
|
+
bidirectional traffic with timestamps.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
logger: Logger instance for the service.
|
|
34
|
+
listen_port: Port to listen on for client connections.
|
|
35
|
+
server_socket: Main server socket for accepting connections.
|
|
36
|
+
is_running: Flag indicating if proxy is running.
|
|
37
|
+
active_connections: Dictionary of active connection information.
|
|
38
|
+
connection_counter: Counter for connection IDs.
|
|
39
|
+
cli_config: Conbus client configuration.
|
|
40
|
+
target_ip: Target server IP address.
|
|
41
|
+
target_port: Target server port number.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
cli_config: ConbusClientConfig,
|
|
47
|
+
listen_port: int,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize the Conbus reverse proxy service.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
cli_config: Conbus client configuration.
|
|
53
|
+
listen_port: Port to listen on for client connections.
|
|
54
|
+
"""
|
|
55
|
+
# Set up logging first
|
|
56
|
+
self.logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
self.listen_port = listen_port
|
|
59
|
+
self.server_socket: Optional[socket.socket] = None
|
|
60
|
+
self.is_running = False
|
|
61
|
+
self.active_connections: Dict[str, dict] = {}
|
|
62
|
+
self.connection_counter = 0
|
|
63
|
+
|
|
64
|
+
# Target server configuration
|
|
65
|
+
self.cli_config = cli_config
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def target_ip(self) -> str:
|
|
69
|
+
"""Get target server IP.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Target server IP address.
|
|
73
|
+
"""
|
|
74
|
+
return self.cli_config.conbus.ip
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def target_port(self) -> int:
|
|
78
|
+
"""Get target server port.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Target server port number.
|
|
82
|
+
"""
|
|
83
|
+
return self.cli_config.conbus.port
|
|
84
|
+
|
|
85
|
+
def start_proxy(self) -> Response:
|
|
86
|
+
"""Start the reverse proxy server.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Response object with success status and proxy details.
|
|
90
|
+
"""
|
|
91
|
+
if self.is_running:
|
|
92
|
+
return Response(
|
|
93
|
+
success=False, data=None, error="Reverse proxy is already running"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Create TCP socket
|
|
98
|
+
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
99
|
+
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
100
|
+
|
|
101
|
+
# Bind to listen port on all interfaces
|
|
102
|
+
self.server_socket.bind(("0.0.0.0", self.listen_port))
|
|
103
|
+
self.server_socket.listen(5) # Allow multiple connections in queue
|
|
104
|
+
|
|
105
|
+
self.is_running = True
|
|
106
|
+
self.logger.info(f"Reverse proxy started on port {self.listen_port}")
|
|
107
|
+
self.logger.info(
|
|
108
|
+
f"Forwarding to {self.cli_config.conbus.ip}:{self.cli_config.conbus.port}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Print startup message
|
|
112
|
+
print(f"Conbus Reverse Proxy started on port {self.listen_port}")
|
|
113
|
+
print(
|
|
114
|
+
f"Forwarding telegrams to {self.cli_config.conbus.ip}:{self.cli_config.conbus.port}"
|
|
115
|
+
)
|
|
116
|
+
print("Monitoring all traffic...\n")
|
|
117
|
+
|
|
118
|
+
# Start accepting connections in background thread
|
|
119
|
+
accept_thread = threading.Thread(
|
|
120
|
+
target=self._accept_connections, daemon=True
|
|
121
|
+
)
|
|
122
|
+
accept_thread.start()
|
|
123
|
+
|
|
124
|
+
return Response(
|
|
125
|
+
success=True,
|
|
126
|
+
data={
|
|
127
|
+
"listen_port": self.listen_port,
|
|
128
|
+
"target_ip": self.cli_config.conbus.ip,
|
|
129
|
+
"target_port": self.cli_config.conbus.port,
|
|
130
|
+
"message": "Reverse proxy started successfully",
|
|
131
|
+
},
|
|
132
|
+
error=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
self.logger.error(f"Failed to start reverse proxy: {e}")
|
|
137
|
+
return Response(
|
|
138
|
+
success=False, data=None, error=f"Failed to start reverse proxy: {e}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def stop_proxy(self) -> Response:
|
|
142
|
+
"""Stop the reverse proxy server.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Response object with success status.
|
|
146
|
+
"""
|
|
147
|
+
if not self.is_running:
|
|
148
|
+
return Response(
|
|
149
|
+
success=False, data=None, error="Reverse proxy is not running"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
self.is_running = False
|
|
153
|
+
|
|
154
|
+
# Close all active connections
|
|
155
|
+
for conn_id, conn_info in list(self.active_connections.items()):
|
|
156
|
+
self._close_connection_pair(conn_id)
|
|
157
|
+
|
|
158
|
+
# Close server socket
|
|
159
|
+
if self.server_socket:
|
|
160
|
+
try:
|
|
161
|
+
self.server_socket.close()
|
|
162
|
+
self.logger.info("Reverse proxy stopped")
|
|
163
|
+
print("Reverse proxy stopped")
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.logger.error(f"Error closing server socket: {e}")
|
|
166
|
+
|
|
167
|
+
return Response(
|
|
168
|
+
success=True,
|
|
169
|
+
data={"message": "Reverse proxy stopped successfully"},
|
|
170
|
+
error=None,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def get_status(self) -> Response:
|
|
174
|
+
"""Get current proxy status and active connections.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Response object with proxy status and connection details.
|
|
178
|
+
"""
|
|
179
|
+
return Response(
|
|
180
|
+
success=True,
|
|
181
|
+
data={
|
|
182
|
+
"running": self.is_running,
|
|
183
|
+
"listen_port": self.listen_port,
|
|
184
|
+
"target_ip": self.cli_config.conbus.ip,
|
|
185
|
+
"target_port": self.cli_config.conbus.port,
|
|
186
|
+
"active_connections": len(self.active_connections),
|
|
187
|
+
"connections": {
|
|
188
|
+
conn_id: {
|
|
189
|
+
"client_address": info["client_address"],
|
|
190
|
+
"connected_at": info["connected_at"].isoformat(),
|
|
191
|
+
"bytes_relayed": info.get("bytes_relayed", 0),
|
|
192
|
+
}
|
|
193
|
+
for conn_id, info in self.active_connections.items()
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
error=None,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _accept_connections(self) -> None:
|
|
200
|
+
"""Accept and handle client connections."""
|
|
201
|
+
while self.is_running:
|
|
202
|
+
try:
|
|
203
|
+
# Accept connection
|
|
204
|
+
if self.server_socket is None:
|
|
205
|
+
break
|
|
206
|
+
client_socket, client_address = self.server_socket.accept()
|
|
207
|
+
|
|
208
|
+
# Generate connection ID
|
|
209
|
+
self.connection_counter += 1
|
|
210
|
+
conn_id = f"conn_{self.connection_counter}"
|
|
211
|
+
|
|
212
|
+
self.logger.info(f"Client connected from {client_address} [{conn_id}]")
|
|
213
|
+
print(
|
|
214
|
+
f"{self.timestamp()} [CONNECTION] Client {client_address} connected [{conn_id}]"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Handle client in separate thread
|
|
218
|
+
client_thread = threading.Thread(
|
|
219
|
+
target=self._handle_client,
|
|
220
|
+
args=(client_socket, client_address, conn_id),
|
|
221
|
+
daemon=True,
|
|
222
|
+
)
|
|
223
|
+
client_thread.start()
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
if self.is_running:
|
|
227
|
+
self.logger.error(f"Error accepting connection: {e}")
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
def _handle_client(
|
|
231
|
+
self, client_socket: socket.socket, client_address: tuple, conn_id: str
|
|
232
|
+
) -> None:
|
|
233
|
+
"""Handle individual client connection with server relay.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
client_socket: Client socket connection.
|
|
237
|
+
client_address: Client address tuple (ip, port).
|
|
238
|
+
conn_id: Connection identifier.
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
# Connect to target server
|
|
242
|
+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
243
|
+
server_socket.settimeout(self.cli_config.conbus.timeout)
|
|
244
|
+
server_socket.connect(
|
|
245
|
+
(self.cli_config.conbus.ip, self.cli_config.conbus.port)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Store connection info
|
|
249
|
+
self.active_connections[conn_id] = {
|
|
250
|
+
"client_socket": client_socket,
|
|
251
|
+
"server_socket": server_socket,
|
|
252
|
+
"client_address": client_address,
|
|
253
|
+
"connected_at": datetime.now(),
|
|
254
|
+
"bytes_relayed": 0,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
self.logger.info(
|
|
258
|
+
f"Connected to target server {self.cli_config.conbus.ip}:{self.cli_config.conbus.port} [{conn_id}]"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Set timeouts for idle connections
|
|
262
|
+
client_socket.settimeout(30.0)
|
|
263
|
+
server_socket.settimeout(30.0)
|
|
264
|
+
|
|
265
|
+
# Start bidirectional relay threads
|
|
266
|
+
client_to_server_thread = threading.Thread(
|
|
267
|
+
target=self._relay_data,
|
|
268
|
+
args=(
|
|
269
|
+
client_socket,
|
|
270
|
+
server_socket,
|
|
271
|
+
"CLIENT→PROXY",
|
|
272
|
+
"PROXY→SERVER",
|
|
273
|
+
conn_id,
|
|
274
|
+
),
|
|
275
|
+
daemon=True,
|
|
276
|
+
)
|
|
277
|
+
server_to_client_thread = threading.Thread(
|
|
278
|
+
target=self._relay_data,
|
|
279
|
+
args=(
|
|
280
|
+
server_socket,
|
|
281
|
+
client_socket,
|
|
282
|
+
"SERVER→PROXY",
|
|
283
|
+
"PROXY→CLIENT",
|
|
284
|
+
conn_id,
|
|
285
|
+
),
|
|
286
|
+
daemon=True,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
client_to_server_thread.start()
|
|
290
|
+
server_to_client_thread.start()
|
|
291
|
+
|
|
292
|
+
# Wait for either thread to finish (indicating connection closure)
|
|
293
|
+
client_to_server_thread.join()
|
|
294
|
+
server_to_client_thread.join()
|
|
295
|
+
|
|
296
|
+
except socket.timeout:
|
|
297
|
+
self.logger.info(f"Connection to target server timed out [{conn_id}]")
|
|
298
|
+
print(
|
|
299
|
+
f"{self.timestamp()} [ERROR] Connection to target server timed out [{conn_id}]"
|
|
300
|
+
)
|
|
301
|
+
except Exception as e:
|
|
302
|
+
self.logger.error(
|
|
303
|
+
f"Error handling client {client_address}: {e} [{conn_id}]"
|
|
304
|
+
)
|
|
305
|
+
print(f"{self.timestamp()} [ERROR] Connection error: {e} [{conn_id}]")
|
|
306
|
+
finally:
|
|
307
|
+
self._close_connection_pair(conn_id)
|
|
308
|
+
|
|
309
|
+
def _relay_data(
|
|
310
|
+
self,
|
|
311
|
+
source_socket: socket.socket,
|
|
312
|
+
dest_socket: socket.socket,
|
|
313
|
+
source_label: str,
|
|
314
|
+
dest_label: str,
|
|
315
|
+
conn_id: str,
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Relay data between sockets with telegram monitoring.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
source_socket: Source socket to receive from.
|
|
321
|
+
dest_socket: Destination socket to send to.
|
|
322
|
+
source_label: Label for source in logs.
|
|
323
|
+
dest_label: Label for destination in logs.
|
|
324
|
+
conn_id: Connection identifier.
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
while self.is_running:
|
|
328
|
+
# Receive data from source
|
|
329
|
+
data = source_socket.recv(1024)
|
|
330
|
+
if not data:
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
# Decode and print telegram
|
|
334
|
+
try:
|
|
335
|
+
message = data.decode("latin-1").strip()
|
|
336
|
+
if message:
|
|
337
|
+
print(f"{self.timestamp()} [{source_label}] {message}")
|
|
338
|
+
|
|
339
|
+
# Forward to destination
|
|
340
|
+
dest_socket.send(data)
|
|
341
|
+
print(f"{self.timestamp()} [{dest_label}] {message}")
|
|
342
|
+
|
|
343
|
+
# Update bytes relayed counter
|
|
344
|
+
if conn_id in self.active_connections:
|
|
345
|
+
self.active_connections[conn_id]["bytes_relayed"] += len(
|
|
346
|
+
data
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
except UnicodeDecodeError:
|
|
350
|
+
# Handle binary data
|
|
351
|
+
print(
|
|
352
|
+
f"{self.timestamp()} [{source_label}] <binary data: {len(data)} bytes>"
|
|
353
|
+
)
|
|
354
|
+
dest_socket.send(data)
|
|
355
|
+
print(
|
|
356
|
+
f"{self.timestamp()} [{dest_label}] <binary data: {len(data)} bytes>"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if conn_id in self.active_connections:
|
|
360
|
+
self.active_connections[conn_id]["bytes_relayed"] += len(data)
|
|
361
|
+
|
|
362
|
+
except socket.timeout:
|
|
363
|
+
self.logger.debug(f"Socket timeout in relay [{conn_id}]")
|
|
364
|
+
except Exception as e:
|
|
365
|
+
if self.is_running:
|
|
366
|
+
self.logger.error(f"Error in data relay: {e} [{conn_id}]")
|
|
367
|
+
|
|
368
|
+
def _close_connection_pair(self, conn_id: str) -> None:
|
|
369
|
+
"""Close both client and server sockets for a connection.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
conn_id: Connection identifier.
|
|
373
|
+
"""
|
|
374
|
+
if conn_id not in self.active_connections:
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
conn_info = self.active_connections[conn_id]
|
|
378
|
+
|
|
379
|
+
# Close client socket
|
|
380
|
+
try:
|
|
381
|
+
if "client_socket" in conn_info:
|
|
382
|
+
conn_info["client_socket"].close()
|
|
383
|
+
except Exception as e:
|
|
384
|
+
self.logger.error(f"Error closing client socket: {e} [{conn_id}]")
|
|
385
|
+
|
|
386
|
+
# Close server socket
|
|
387
|
+
try:
|
|
388
|
+
if "server_socket" in conn_info:
|
|
389
|
+
conn_info["server_socket"].close()
|
|
390
|
+
except Exception as e:
|
|
391
|
+
self.logger.error(f"Error closing server socket: {e} [{conn_id}]")
|
|
392
|
+
|
|
393
|
+
# Log disconnection
|
|
394
|
+
client_address = conn_info.get("client_address", "unknown")
|
|
395
|
+
bytes_relayed = conn_info.get("bytes_relayed", 0)
|
|
396
|
+
|
|
397
|
+
self.logger.info(
|
|
398
|
+
f"Client {client_address} disconnected [{conn_id}] - {bytes_relayed} bytes relayed"
|
|
399
|
+
)
|
|
400
|
+
print(
|
|
401
|
+
f"{self.timestamp()} [DISCONNECTION] "
|
|
402
|
+
f"Client {client_address} "
|
|
403
|
+
f"disconnected [{conn_id}] - "
|
|
404
|
+
f"{bytes_relayed} bytes relayed"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Remove from active connections
|
|
408
|
+
del self.active_connections[conn_id]
|
|
409
|
+
|
|
410
|
+
@staticmethod
|
|
411
|
+
def timestamp() -> str:
|
|
412
|
+
"""Generate timestamp string for logging.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Timestamp string in HH:MM:SS,mmm format.
|
|
416
|
+
"""
|
|
417
|
+
return datetime.now().strftime("%H:%M:%S,%f")[:-3]
|
|
418
|
+
|
|
419
|
+
def run_blocking(self) -> None:
|
|
420
|
+
"""Run the proxy in blocking mode (for CLI usage).
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ReverseProxyError: If proxy fails to start.
|
|
424
|
+
"""
|
|
425
|
+
result = self.start_proxy()
|
|
426
|
+
if not result.success:
|
|
427
|
+
raise ReverseProxyError(result.error)
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
# Keep running until interrupted
|
|
431
|
+
while self.is_running:
|
|
432
|
+
time.sleep(1)
|
|
433
|
+
except KeyboardInterrupt:
|
|
434
|
+
print(f"\n{self.timestamp()} [SHUTDOWN] Received interrupt signal")
|
|
435
|
+
self.stop_proxy()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Server services for XP protocol variants."""
|