This commit is contained in:
commit
83dfdce5bd
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/tcp_splitter.iml" filepath="$PROJECT_DIR$/.idea/tcp_splitter.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"listen_host": "0.0.0.0",
|
||||||
|
"listen_port": 10001,
|
||||||
|
"target_hosts": [
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 12001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 12002
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"run_duration": 600,
|
||||||
|
"log_level": "INFO"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# TCP Splitter
|
||||||
|
Et Python-basert verktøy som splitter innkommende TCP-trafikk til flere mål samtidig.
|
||||||
|
|
||||||
|
## Start
|
||||||
|
|
||||||
|
Kjør `tcp_splitter.py` for å starte programmet.
|
||||||
|
Scriptet `worker.py` blir startet automatisk av hovedscriptet, og restartes med jevne mellomrom basert på verdien satt i `config.json`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Konfigurasjon (`config.json`)
|
||||||
|
listen_host / listen_port: Hvor splitteren lytter etter innkommende forbindelser.
|
||||||
|
target_hosts: Liste over adresser og porter som mottar kopier av trafikken.
|
||||||
|
run_duration: Hvor lenge (sekunder) splitteren skal kjøre før alle forbindelser restartes.
|
||||||
|
log_level: Velg mellom INFO, DEBUG eller ERROR
|
||||||
|
|
||||||
|
### Eksempel:
|
||||||
|
{
|
||||||
|
"listen_host": "0.0.0.0",
|
||||||
|
"listen_port": 10001,
|
||||||
|
"target_hosts": [
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 12001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 12002
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"run_duration": 600,
|
||||||
|
"log_level": "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
project-root/
|
||||||
|
├── tcp_splitter.py
|
||||||
|
├── worker.py
|
||||||
|
├── config.json
|
||||||
|
└── README.md
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Get the current time and print it when starting the process
|
||||||
|
restart_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
|
print(f"Starting worker script at {restart_time}")
|
||||||
|
|
||||||
|
# Start the worker script in a new Command Prompt window with a custom title
|
||||||
|
process = subprocess.Popen(['start', 'cmd', '/c', 'title TCP_SPLITTER && worker.exe'], shell=True)
|
||||||
|
|
||||||
|
# Wait a moment to allow the process to start
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Check if the specific titled process is still running
|
||||||
|
result = subprocess.run('tasklist /v | findstr "TCP_SPLITTER"', shell=True, stdout=subprocess.PIPE, text=True)
|
||||||
|
|
||||||
|
# If "TCP_SPLITTER" title is not found, the worker script has stopped
|
||||||
|
if "TCP_SPLITTER" not in result.stdout:
|
||||||
|
exit_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
|
print(f"Worker script exited at {exit_time}. Restarting...")
|
||||||
|
break # Exit the inner loop to restart the outer loop and launch the script again
|
||||||
|
|
||||||
|
# Check every second if the script is still running
|
||||||
|
time.sleep(1)
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from crccheck.crc import CrcArc
|
||||||
|
|
||||||
|
# Load configuration from config.json
|
||||||
|
with open('config.json', 'r') as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
|
||||||
|
# Configuration from config.json
|
||||||
|
LISTEN_HOST = config['listen_host']
|
||||||
|
LISTEN_PORT = config['listen_port']
|
||||||
|
TARGET_HOSTS = config['target_hosts']
|
||||||
|
RUN_DURATION = config['run_duration']
|
||||||
|
LOG_LEVEL = config['log_level']
|
||||||
|
|
||||||
|
# Map log level strings to logging module constants
|
||||||
|
log_level_mapping = {
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"CRITICAL": logging.CRITICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate and set the log level
|
||||||
|
if LOG_LEVEL not in log_level_mapping:
|
||||||
|
raise ValueError(f"Invalid log level: {LOG_LEVEL}. Valid options are: {list(log_level_mapping.keys())}")
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
logging.basicConfig(
|
||||||
|
level=log_level_mapping[LOG_LEVEL],
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('relay_server.log', encoding='utf-8'), # Log to a file
|
||||||
|
logging.StreamHandler() # Log to the console
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def relay_to_server(server_host, server_port, data):
|
||||||
|
"""Relay data to a server and return the response."""
|
||||||
|
try:
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
|
||||||
|
server_socket.connect((server_host, server_port))
|
||||||
|
server_socket.sendall(data)
|
||||||
|
response = server_socket.recv(4096)
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to relay data to {server_host}:{server_port}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def calculate_crc(data):
|
||||||
|
"""Calculate CRC using CrcArc."""
|
||||||
|
crc = CrcArc.calc(data.encode()) # Encode data to bytes
|
||||||
|
crc_hex = hex(crc).split('x')[1].upper().zfill(4) # Format as uppercase hex (4 chars)
|
||||||
|
return crc_hex
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_length(data):
|
||||||
|
"""Calculate the length of the data in hexadecimal."""
|
||||||
|
length = len(data.encode()) # Length in bytes
|
||||||
|
length_hex = hex(length)[2:].upper().zfill(4) # Convert to hex, ensure 4 digits
|
||||||
|
return length_hex
|
||||||
|
|
||||||
|
def handle_client(client_socket, client_address):
|
||||||
|
"""Handle a client connection."""
|
||||||
|
try:
|
||||||
|
# Receive data from the client
|
||||||
|
data = client_socket.recv(4096)
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"No data received from {client_address}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(f"Received data from {client_address}: {data}")
|
||||||
|
|
||||||
|
# Decode the data and check for NULL message
|
||||||
|
plain_data = data.decode("utf-8", errors='ignore')
|
||||||
|
# Validate message integrity
|
||||||
|
if not "SIA-DCS" in plain_data or not "ADM-CID" in plain_data:
|
||||||
|
return
|
||||||
|
if '"NULL"' in plain_data:
|
||||||
|
logger.debug("NULL Message detected, sending ACK without relaying signal!")
|
||||||
|
orig_text = plain_data[15:].strip()
|
||||||
|
ack_text = '"ACK"' + orig_text
|
||||||
|
ack_text_bytes = ack_text.encode()
|
||||||
|
logger.debug(f"ACK Message: {ack_text}")
|
||||||
|
|
||||||
|
# Calculate CRC and format it
|
||||||
|
crc = CrcArc.calc(ack_text_bytes)
|
||||||
|
crcstr = str(hex(crc)).split('x')
|
||||||
|
crcstr = str(crcstr[1].upper())
|
||||||
|
if len(crcstr) == 2:
|
||||||
|
crcstr = '00' + crcstr
|
||||||
|
if len(crcstr) == 3:
|
||||||
|
crcstr = '0' + crcstr
|
||||||
|
|
||||||
|
# Calculate length and format it
|
||||||
|
length = str(hex(len(ack_text_bytes))).split('x')
|
||||||
|
logger.debug(f"CRC & Length: {crcstr}, 00{length[1]}")
|
||||||
|
# Construct the ACK message
|
||||||
|
ack_msg = '\n' + crcstr + '00' + length[1].upper() + ack_text + '\r'
|
||||||
|
|
||||||
|
# Send the ACK response
|
||||||
|
ack_bytes = bytes(ack_msg, 'ASCII')
|
||||||
|
client_socket.sendall(ack_bytes)
|
||||||
|
logger.debug(f"Response sent to client: {ack_msg.strip()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Relay the data to all target hosts
|
||||||
|
responses = []
|
||||||
|
for target in TARGET_HOSTS:
|
||||||
|
logger.info(f"Relaying data to {target['host']}:{target['port']}")
|
||||||
|
response = relay_to_server(target['host'], target['port'], data)
|
||||||
|
if response:
|
||||||
|
responses.append(response)
|
||||||
|
logger.debug(f"Received response from {target['host']}:{target['port']}: {response}")
|
||||||
|
|
||||||
|
# Send only the first server's response back to the client
|
||||||
|
if responses:
|
||||||
|
client_socket.sendall(responses[0])
|
||||||
|
logger.info(f"Sent response to {client_address}: {responses[0]}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No responses received from target hosts for {client_address}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling client {client_address}: {e}")
|
||||||
|
finally:
|
||||||
|
client_socket.close()
|
||||||
|
logger.debug(f"Closed connection with {client_address}")
|
||||||
|
|
||||||
|
def start_relay():
|
||||||
|
"""Start the TCP relay server."""
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
|
||||||
|
server_socket.bind((LISTEN_HOST, LISTEN_PORT))
|
||||||
|
server_socket.listen(5)
|
||||||
|
logger.info(f"Relay server started listening on {LISTEN_HOST}:{LISTEN_PORT}")
|
||||||
|
|
||||||
|
# Set a timer to stop the server after RUN_DURATION seconds
|
||||||
|
stop_time = time.time() + RUN_DURATION
|
||||||
|
logger.debug(f"Server will run for {RUN_DURATION} seconds.")
|
||||||
|
|
||||||
|
while time.time() < stop_time:
|
||||||
|
try:
|
||||||
|
# Set a timeout to periodically check if the run duration has elapsed
|
||||||
|
server_socket.settimeout(1)
|
||||||
|
client_socket, client_address = server_socket.accept()
|
||||||
|
logger.debug(f"Accepted connection from {client_address}")
|
||||||
|
client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
|
||||||
|
client_thread.start()
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error accepting connection: {e}")
|
||||||
|
|
||||||
|
logger.info("Server run duration elapsed. Shutting down.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_relay()
|
||||||
Loading…
Reference in New Issue