2025-11-17 12:04:17 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
(c) 2025 Rainer Fiedler - do6uk
|
|
|
|
|
https://github.com/do6uk/meshcore_multitcp
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import threading
|
|
|
|
|
import socket
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import signal
|
|
|
|
|
import argparse
|
|
|
|
|
import traceback
|
|
|
|
|
import json
|
|
|
|
|
import time
|
2025-12-20 15:41:59 +01:00
|
|
|
import sqlite3
|
2025-11-17 12:04:17 +01:00
|
|
|
from typing import Any, Dict
|
|
|
|
|
from meshcore_multitcp_packets import BinaryReqType, PacketType, CmdPacketType
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
APP = "MeshCore multitcp-proxy"
|
|
|
|
|
VERSION = "1.1"
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
import logging
|
|
|
|
|
FORMAT = '%(asctime)-15s %(levelname)-10s %(message)s'
|
|
|
|
|
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
client_host = "0.0.0.0"
|
|
|
|
|
client_port = 5000
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
forward_host = "0.0.0.0"
|
|
|
|
|
forward_port = 5001
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
device_host = "192.168.5.62"
|
|
|
|
|
device_port = 5000
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
sqlite_active = False
|
|
|
|
|
sqlite_file = 'meshcore_multitcp.db'
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
clients = []
|
|
|
|
|
client_names = {}
|
|
|
|
|
threads = []
|
2025-12-20 15:41:59 +01:00
|
|
|
client_fwd = {}
|
|
|
|
|
client_fwd_active = {}
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
stop_device = False
|
|
|
|
|
device = None
|
|
|
|
|
|
|
|
|
|
#ignore_packets_device = [PacketType.BATTERY.value, PacketType.ADVERTISEMENT.value]
|
2025-12-20 15:41:59 +01:00
|
|
|
#ignore_packets_device = [PacketType.NO_MORE_MSGS.value, PacketType.OK.value, PacketType.BATTERY.value, PacketType.DEVICE_INFO.value, PacketType.LOG_DATA.value]
|
|
|
|
|
ignore_packets_device = [PacketType.OK.value, PacketType.BATTERY.value, PacketType.DEVICE_INFO.value, PacketType.LOG_DATA.value]
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
#ignore_packets_app = [CmdPacketType.GET_BATT_AND_STORAGE.value]
|
|
|
|
|
ignore_packets_app = []
|
|
|
|
|
|
|
|
|
|
app_frame_started = False
|
|
|
|
|
app_frame_size = 0
|
|
|
|
|
app_header = b""
|
|
|
|
|
app_inframe = b""
|
|
|
|
|
|
|
|
|
|
device_frame_started = False
|
|
|
|
|
device_frame_size = 0
|
|
|
|
|
device_header = b""
|
|
|
|
|
device_inframe = b""
|
|
|
|
|
|
|
|
|
|
contact_nb = 0
|
|
|
|
|
contacts = {}
|
|
|
|
|
channels = {}
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
def db_create():
|
|
|
|
|
global sqlite_file
|
|
|
|
|
|
|
|
|
|
logger.info(f"SQLITE: creating {sqlite_file} ...")
|
|
|
|
|
con = sqlite3.connect(sqlite_file)
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
cur.execute('CREATE TABLE packets (timestamp INTEGER, type INTEGER, data BLOB)')
|
|
|
|
|
cur.execute('CREATE TABLE clients (hash TEXT, ip TEXT, client TEXT, last_timestamp INTEGER)')
|
|
|
|
|
con.commit()
|
|
|
|
|
con.close()
|
|
|
|
|
|
|
|
|
|
def db_store_packet(packet_timestamp, packet_type, data):
|
|
|
|
|
global sqlite_file
|
|
|
|
|
|
|
|
|
|
logger.debug(f"SQLITE: storing message to {sqlite_file} ...")
|
|
|
|
|
con = sqlite3.connect(sqlite_file)
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
cur.execute(f"INSERT INTO packets (timestamp, type, data) VALUES ({packet_timestamp}, {packet_type}, ?)", (data,))
|
|
|
|
|
con.commit()
|
|
|
|
|
con.close()
|
|
|
|
|
|
|
|
|
|
def db_load_packet(ip,clientname):
|
|
|
|
|
global sqlite_file, client_fwd
|
|
|
|
|
|
|
|
|
|
if client_fwd[ip]:
|
|
|
|
|
logger.debug(f"SQLITE: {ip} ({clientname}) messages already synced")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
logger.info(f"SQLITE: restoring data from {sqlite_file} for {clientname}@{ip}...")
|
|
|
|
|
|
|
|
|
|
con = sqlite3.connect(sqlite_file)
|
|
|
|
|
con.text_factory = bytes
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
res = cur.execute(f"SELECT last_timestamp FROM clients WHERE ip = ? AND client = ?",(ip,clientname))
|
|
|
|
|
row = res.fetchone()
|
|
|
|
|
if row:
|
|
|
|
|
timestamp = row[0]
|
|
|
|
|
else:
|
|
|
|
|
cur.execute(f"INSERT INTO clients (hash, ip, client, last_timestamp) VALUES ('',?,?,0)",(ip,clientname))
|
|
|
|
|
con.commit()
|
|
|
|
|
timestamp = 0
|
|
|
|
|
|
|
|
|
|
logger.debug(f"SQLITE: {ip} ({clientname}) requests stored messages after {timestamp}")
|
|
|
|
|
res = cur.execute(f"SELECT data FROM packets WHERE timestamp > {timestamp}")
|
|
|
|
|
rows = res.fetchall()
|
|
|
|
|
|
|
|
|
|
data = []
|
|
|
|
|
for row in rows:
|
|
|
|
|
data.append(row[0])
|
|
|
|
|
|
|
|
|
|
con.close()
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
def db_set_client_time(ip,clientname):
|
|
|
|
|
global sqlite_file
|
|
|
|
|
|
|
|
|
|
logger.debug(f"SQLITE: set timestamp for {clientname}@{ip} ...")
|
|
|
|
|
|
|
|
|
|
con = sqlite3.connect(sqlite_file)
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
cur.execute(f"UPDATE clients SET last_timestamp = ? WHERE client = ? AND ip = ?", (time.time(),clientname,ip))
|
|
|
|
|
con.commit()
|
|
|
|
|
con.close()
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
def get_ip(sock_handle):
|
2025-12-08 11:14:06 +01:00
|
|
|
try:
|
|
|
|
|
host, port = sock_handle.getpeername()
|
|
|
|
|
return host
|
|
|
|
|
except:
|
|
|
|
|
logger.error(f"[get_ip] while getpeername()")
|
|
|
|
|
return False
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
def get_ip_port(sock_handle):
|
2025-12-08 11:14:06 +01:00
|
|
|
try:
|
|
|
|
|
host, port = sock_handle.getpeername()
|
|
|
|
|
return host+':'+str(port)
|
|
|
|
|
except:
|
|
|
|
|
logger.error(f"[get_ip_port] while getpeername()")
|
|
|
|
|
return False
|
2025-11-17 12:04:17 +01:00
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
def get_ip_client_name(sock_handle):
|
|
|
|
|
global client_names
|
|
|
|
|
try:
|
|
|
|
|
host, port = sock_handle.getpeername()
|
|
|
|
|
except:
|
|
|
|
|
logger.error(f"[get_ip_client_name] while getpeername()")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
client_name = host+' ('+client_names[host+':'+str(port)]+')'
|
|
|
|
|
except:
|
|
|
|
|
client_name = host+':'+str(port)
|
|
|
|
|
|
|
|
|
|
return client_name
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
def get_client_name(sock_handle):
|
|
|
|
|
global client_names
|
2025-12-08 11:14:06 +01:00
|
|
|
try:
|
|
|
|
|
host, port = sock_handle.getpeername()
|
|
|
|
|
except:
|
|
|
|
|
logger.error(f"[get_client_name] while getpeername()")
|
|
|
|
|
return False
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
try:
|
2025-12-20 15:41:59 +01:00
|
|
|
client_name = client_names[host+':'+str(port)]
|
2025-11-17 12:04:17 +01:00
|
|
|
except:
|
|
|
|
|
client_name = host+':'+str(port)
|
|
|
|
|
|
|
|
|
|
return client_name
|
|
|
|
|
|
|
|
|
|
def ip_to_tuple(ip):
|
|
|
|
|
ip, port = ip.split(':')
|
|
|
|
|
return (ip, int(port))
|
|
|
|
|
|
|
|
|
|
def handle_app_data(data: bytearray):
|
|
|
|
|
if len(data) >= 3:
|
|
|
|
|
frame_header = data[:3]
|
|
|
|
|
frame_size = int.from_bytes(frame_header[1:], byteorder="little")
|
|
|
|
|
frame_data = data[3:]
|
|
|
|
|
logger.debug(f"[handle_app_data] header: {frame_header} size: {frame_size} data: {frame_data}")
|
|
|
|
|
|
|
|
|
|
packet_type_value = data[3]
|
2025-12-20 15:41:59 +01:00
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
if packet_type_value in ignore_packets_app:
|
|
|
|
|
logger.debug(f"[handle_app_data] packet {CmdPacketType(packet_type_value).name} ignored")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if CmdPacketType.exists(packet_type_value):
|
|
|
|
|
packet_type = CmdPacketType(packet_type_value).name
|
|
|
|
|
logger.debug(f"[handle_app_data] packet {packet_type} found ...")
|
|
|
|
|
return packet_type
|
|
|
|
|
|
|
|
|
|
elif len(data) == 0:
|
|
|
|
|
logger.debug(f"[handle_app_data] connection lost")
|
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"[handle_app_data] header_len_error | data: {data}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def handle_device_data(data: bytearray):
|
|
|
|
|
if len(data) >= 3:
|
|
|
|
|
frame_header = data[:3]
|
|
|
|
|
frame_size = int.from_bytes(frame_header[1:], byteorder="little")
|
|
|
|
|
frame_data = data[3:]
|
|
|
|
|
logger.debug(f"[handle_device_data] header: {frame_header} size: {frame_size} data: {frame_data}")
|
2025-12-20 15:41:59 +01:00
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
packet_type_value = data[3]
|
|
|
|
|
logger.debug(f"[parse_device_data] raw-data: {data.hex()}")
|
2025-12-20 15:41:59 +01:00
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
if packet_type_value in ignore_packets_device:
|
|
|
|
|
logger.debug(f"[parse_device_data] packet {PacketType(packet_type_value).name} ignored")
|
|
|
|
|
return False
|
2025-12-20 15:41:59 +01:00
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
if PacketType.exists(packet_type_value):
|
|
|
|
|
packet_type = PacketType(packet_type_value).name
|
|
|
|
|
logger.debug(f"[parse_device_data] packet {packet_type} found ...")
|
2025-12-20 15:41:59 +01:00
|
|
|
|
|
|
|
|
if packet_type_value in [7,8,16,17] and sqlite_active:
|
|
|
|
|
db_store_packet(time.time(),packet_type_value,data)
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
return packet_type
|
|
|
|
|
|
|
|
|
|
elif len(data) == 0:
|
|
|
|
|
logger.debug(f"[handle_device_data] connection lost")
|
|
|
|
|
return -1
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"[handle_device_data] header_len_error | data: {data}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_status(data, pubkey_prefix=None, offset=0):
|
|
|
|
|
res = {}
|
|
|
|
|
|
|
|
|
|
# Handle pubkey
|
|
|
|
|
if pubkey_prefix is None:
|
|
|
|
|
# Extract from data (format 1)
|
|
|
|
|
res["pubkey_pre"] = data[2:8].hex()
|
|
|
|
|
offset = 8 # Fields start at offset 8
|
|
|
|
|
else:
|
|
|
|
|
# Use provided prefix (format 2)
|
|
|
|
|
res["pubkey_pre"] = pubkey_prefix
|
|
|
|
|
# offset stays as provided (typically 0)
|
|
|
|
|
|
|
|
|
|
# Parse all fields with the given offset
|
|
|
|
|
res["bat"] = int.from_bytes(data[offset:offset+2], byteorder="little")
|
|
|
|
|
res["tx_queue_len"] = int.from_bytes(data[offset+2:offset+4], byteorder="little")
|
|
|
|
|
res["noise_floor"] = int.from_bytes(data[offset+4:offset+6], byteorder="little", signed=True)
|
|
|
|
|
res["last_rssi"] = int.from_bytes(data[offset+6:offset+8], byteorder="little", signed=True)
|
|
|
|
|
res["nb_recv"] = int.from_bytes(data[offset+8:offset+12], byteorder="little", signed=False)
|
|
|
|
|
res["nb_sent"] = int.from_bytes(data[offset+12:offset+16], byteorder="little", signed=False)
|
|
|
|
|
res["airtime"] = int.from_bytes(data[offset+16:offset+20], byteorder="little")
|
|
|
|
|
res["uptime"] = int.from_bytes(data[offset+20:offset+24], byteorder="little")
|
|
|
|
|
res["sent_flood"] = int.from_bytes(data[offset+24:offset+28], byteorder="little")
|
|
|
|
|
res["sent_direct"] = int.from_bytes(data[offset+28:offset+32], byteorder="little")
|
|
|
|
|
res["recv_flood"] = int.from_bytes(data[offset+32:offset+36], byteorder="little")
|
|
|
|
|
res["recv_direct"] = int.from_bytes(data[offset+36:offset+40], byteorder="little")
|
|
|
|
|
res["full_evts"] = int.from_bytes(data[offset+40:offset+42], byteorder="little")
|
|
|
|
|
res["last_snr"] = int.from_bytes(data[offset+42:offset+44], byteorder="little", signed=True) / 4
|
|
|
|
|
res["direct_dups"] = int.from_bytes(data[offset+44:offset+46], byteorder="little")
|
|
|
|
|
res["flood_dups"] = int.from_bytes(data[offset+46:offset+48], byteorder="little")
|
|
|
|
|
res["rx_airtime"] = int.from_bytes(data[offset+48:offset+52], byteorder="little")
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
def device_connect():
|
|
|
|
|
global device
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
device.connect((device_host, device_port))
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def device_handle():
|
|
|
|
|
global stop_device, threads, device_host, device_port
|
|
|
|
|
|
|
|
|
|
count_retry = 0
|
|
|
|
|
|
|
|
|
|
while not stop_device:
|
|
|
|
|
logger.debug(f"DEVICE: connect to {device_host}:{device_port} attemp {count_retry+1} ...")
|
|
|
|
|
|
|
|
|
|
count_retry = count_retry + 1
|
|
|
|
|
device_ready = device_connect()
|
|
|
|
|
if device_ready:
|
|
|
|
|
logger.info(f"DEVICE: ready ...")
|
|
|
|
|
count_retry = 0
|
|
|
|
|
|
|
|
|
|
while device_ready:
|
|
|
|
|
try:
|
|
|
|
|
message = device.recv(1024)
|
|
|
|
|
logger.debug(f"DEVICE: receive raw: {message}")
|
|
|
|
|
message_type = handle_device_data(message)
|
|
|
|
|
|
|
|
|
|
if message_type == -1:
|
|
|
|
|
raise socket.error
|
|
|
|
|
|
|
|
|
|
if message_type and message_type != 'NONE':
|
|
|
|
|
logger.info(f"DEVICE {message_type}")
|
|
|
|
|
|
|
|
|
|
client_forward(message, message_type)
|
2025-12-20 15:41:59 +01:00
|
|
|
|
|
|
|
|
if stop_device:
|
|
|
|
|
return
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
except socket.error:
|
|
|
|
|
logger.error(f"DEVICE: lost connection on read {get_ip(device)}")
|
|
|
|
|
device.close()
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if count_retry == 3:
|
|
|
|
|
stop_device = True
|
|
|
|
|
logger.error(f"DEVICE: re-connect failed {count_retry} times ... exit")
|
|
|
|
|
os.kill(os.getpid(), signal.SIGINT)
|
|
|
|
|
|
|
|
|
|
logger.debug(f"DEVICE: re-connect failed {count_retry} times ... try again")
|
|
|
|
|
time.sleep(2*count_retry)
|
|
|
|
|
|
|
|
|
|
def device_write(message):
|
|
|
|
|
global device, stop_device
|
|
|
|
|
|
|
|
|
|
if not stop_device:
|
|
|
|
|
try:
|
|
|
|
|
logger.debug(f"DEVICE: send raw: {message}")
|
|
|
|
|
device.send(message)
|
|
|
|
|
return True
|
|
|
|
|
|
2025-12-08 11:14:06 +01:00
|
|
|
except (socket.error, BrokenPipeError):
|
2025-11-17 12:04:17 +01:00
|
|
|
logger.error(f"DEVICE: lost connection on write {get_ip(device)}")
|
|
|
|
|
device.close()
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"DEVICE: closed ... exit")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
def client_forward(message, message_type):
|
2025-12-20 15:41:59 +01:00
|
|
|
global client_fwd, client_fwd_active
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
for client in clients:
|
2025-12-20 15:41:59 +01:00
|
|
|
ip = get_ip(client)
|
|
|
|
|
ipclientname = get_ip_client_name(client)
|
2025-11-17 12:04:17 +01:00
|
|
|
if message_type == -1:
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.debug(f"CLIENT {ipclientname} lost connection on Forward")
|
2025-11-17 12:04:17 +01:00
|
|
|
raise socket.error
|
|
|
|
|
elif message_type and message_type != 'NONE':
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.debug(f"CLIENT {ipclientname} Forward {message_type}")
|
2025-11-17 12:04:17 +01:00
|
|
|
else:
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.debug(f"CLIENT {ipclientname} Forward raw-data: {message}")
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
try:
|
2025-12-20 15:41:59 +01:00
|
|
|
count_wait = 0
|
|
|
|
|
while client_fwd_active[ip]:
|
|
|
|
|
count_wait += 1
|
|
|
|
|
logger.debug(f"CLIENT {ipclientname} Forward is waiting while syncing stored messages ({count_wait}/3) ...")
|
|
|
|
|
|
|
|
|
|
if count_wait == 3:
|
|
|
|
|
client_fwd[ip] = True
|
|
|
|
|
client_fwd_active[ip] = False
|
|
|
|
|
logger.info(f"CLIENT {ipclientname} possible crash of message-sync! forward anyway ...")
|
|
|
|
|
|
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
client.send(message)
|
|
|
|
|
|
2025-12-08 11:14:06 +01:00
|
|
|
except (socket.error, BrokenPipeError):
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.error(f"CLIENT {get_ip_client_name(client)} lost connection on write")
|
2025-11-17 12:04:17 +01:00
|
|
|
if client in clients:
|
|
|
|
|
index = clients.index(client)
|
|
|
|
|
clients.remove(client)
|
|
|
|
|
client.close()
|
|
|
|
|
continue
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
def client_receive(client, forwarding = False):
|
|
|
|
|
global client_fwd, client_fwd_active, stop_device
|
|
|
|
|
|
|
|
|
|
while not stop_device:
|
2025-11-17 12:04:17 +01:00
|
|
|
try:
|
2025-12-20 15:41:59 +01:00
|
|
|
ip = get_ip(client)
|
|
|
|
|
ipclientname = get_ip_client_name(client)
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
message = client.recv(1024)
|
|
|
|
|
message_type = handle_app_data(message)
|
|
|
|
|
|
|
|
|
|
if message_type == -1:
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.debug(f"CLIENT {ipclientname} lost connection on send")
|
2025-11-17 12:04:17 +01:00
|
|
|
raise socket.error
|
|
|
|
|
elif message_type:
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.info(f"CLIENT {ipclientname} Sent {message_type}")
|
2025-11-17 12:04:17 +01:00
|
|
|
else:
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.debug(f"CLIENT {ipclientname} Sent raw-data: {message}")
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
if message_type == CmdPacketType.APP_START.name:
|
|
|
|
|
app_ver = message[4]
|
|
|
|
|
app_name = message[11:].decode('utf-8')
|
|
|
|
|
client_names[get_ip_port(client)] = app_name
|
|
|
|
|
logger.debug(f"CLIENT {get_ip(client)} app_name: {app_name}")
|
2025-12-20 15:41:59 +01:00
|
|
|
|
|
|
|
|
if message_type == CmdPacketType.SYNC_NEXT_MESSAGE.name and not client_fwd[ip] and forwarding:
|
|
|
|
|
logger.debug(f"CLIENT {ipclientname} starting message-sync ...")
|
|
|
|
|
client_fwd_active[ip] = True
|
|
|
|
|
|
|
|
|
|
data = db_load_packet(ip, get_client_name(client))
|
|
|
|
|
if data:
|
|
|
|
|
msg_count = len(data)
|
|
|
|
|
logger.info(f"CLIENT {ipclientname} Forward {msg_count} stored messages")
|
|
|
|
|
for packet in data:
|
|
|
|
|
logger.debug(f"CLIENT {ipclientname} Forward stored data: {packet}")
|
|
|
|
|
client.send(packet)
|
|
|
|
|
|
|
|
|
|
message = client.recv(1024)
|
|
|
|
|
logger.debug(f"CLIENT {ipclientname} responds: {message}")
|
|
|
|
|
else:
|
|
|
|
|
logger.info(f"CLIENT {ipclientname} no stored messages to forward")
|
|
|
|
|
|
|
|
|
|
client_fwd[ip] = True
|
|
|
|
|
client_fwd_active[ip] = False
|
|
|
|
|
logger.info(f"CLIENT {ipclientname} finished message-sync")
|
|
|
|
|
|
|
|
|
|
if client_fwd[ip] and forwarding:
|
|
|
|
|
db_set_client_time(ip, get_client_name(client))
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
device_write(message)
|
|
|
|
|
|
2025-12-08 11:14:06 +01:00
|
|
|
except (socket.error, BrokenPipeError):
|
2025-12-20 15:41:59 +01:00
|
|
|
logger.error(f"CLIENT {get_ip_client_name(device)} lost connection on read")
|
2025-11-17 12:04:17 +01:00
|
|
|
if client in clients:
|
|
|
|
|
index = clients.index(client)
|
|
|
|
|
clients.remove(client)
|
|
|
|
|
client.close()
|
|
|
|
|
break
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
def client_connect(server_sock,forwarding = False):
|
|
|
|
|
global clients, client_names, threads, client_fwd, stop_device
|
2025-11-17 12:04:17 +01:00
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
if forwarding:
|
|
|
|
|
info = 'for store-forward '
|
|
|
|
|
else:
|
|
|
|
|
info = ''
|
|
|
|
|
|
|
|
|
|
while not stop_device:
|
|
|
|
|
ip, port = server_sock.getsockname()
|
|
|
|
|
logger.info(f"CLIENT: listening at {ip}:{port} {info}...")
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
try:
|
2025-12-20 15:41:59 +01:00
|
|
|
client, address = server_sock.accept()
|
|
|
|
|
ip = get_ip(client)
|
|
|
|
|
logger.info(f"CLIENT: new connection from {ip}")
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
clients.append(client)
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
client_fwd[ip] = False
|
|
|
|
|
client_fwd_active[ip] = False
|
|
|
|
|
|
|
|
|
|
thread = threading.Thread(target=client_receive, args=(client, forwarding, ), name="CLIENT "+ip)
|
2025-11-17 12:04:17 +01:00
|
|
|
thread.start()
|
|
|
|
|
threads.append(thread)
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
logger.info(f"EXIT after KeyboardInterrupt ...")
|
|
|
|
|
stop_device = True
|
|
|
|
|
|
|
|
|
|
for t in threads:
|
|
|
|
|
logger.debug(f"EXIT: waiting for thread {t.name} to finish ...")
|
|
|
|
|
t.join()
|
|
|
|
|
|
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
|
def device_state(running_threads):
|
|
|
|
|
while True:
|
|
|
|
|
for thread in running_threads:
|
|
|
|
|
if thread.name == "DEVICE_HANDLE":
|
|
|
|
|
print('device_state',thread.is_alive())
|
|
|
|
|
if not thread.is_alive():
|
|
|
|
|
print('device failed')
|
|
|
|
|
os.kill(os.getpid(), signal.SIGINT)
|
|
|
|
|
|
|
|
|
|
time.sleep(2)
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='TCP meshcore proxy')
|
|
|
|
|
parser.add_argument('-s', '--server', required=True, help='Server IP and port, i.e.: 127.0.0.1:5000')
|
2025-12-20 15:41:59 +01:00
|
|
|
parser.add_argument('-f', '--forward', required=False, help='Server IP and port for clients using message storage, i.e.: 127.0.0.1:5001')
|
2025-11-17 12:04:17 +01:00
|
|
|
parser.add_argument('-d', '--device', required=True, help='Device IP and port, i.e.: 127.0.0.2:5000')
|
2025-12-20 15:41:59 +01:00
|
|
|
parser.add_argument('-sql', '--sqlite', action='store_true', help='enable SQLite-function')
|
|
|
|
|
parser.add_argument('-ver', '--version', action='store_true', help='shows current app-version')
|
2025-11-17 12:04:17 +01:00
|
|
|
output_group = parser.add_mutually_exclusive_group()
|
|
|
|
|
output_group.add_argument('-q', '--quiet', action='store_true', help='minimal logging to CLI')
|
|
|
|
|
output_group.add_argument('-v', '--verbose', action='store_true', help='maximum logging to CLI')
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
client_host, client_port = ip_to_tuple(args.server)
|
|
|
|
|
device_host, device_port = ip_to_tuple(args.device)
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
if args.forward:
|
|
|
|
|
forward_host, forward_port = ip_to_tuple(args.forward)
|
|
|
|
|
|
|
|
|
|
logger.info(f"{APP} v{VERSION}")
|
|
|
|
|
|
|
|
|
|
if args.version:
|
|
|
|
|
sys.exit()
|
2025-11-17 12:04:17 +01:00
|
|
|
if args.quiet:
|
|
|
|
|
logger.setLevel(logging.CRITICAL)
|
|
|
|
|
if args.verbose:
|
|
|
|
|
logger.setLevel(logging.DEBUG)
|
2025-12-20 15:41:59 +01:00
|
|
|
if args.sqlite:
|
|
|
|
|
logger.info('SQLITE: enable message storage')
|
|
|
|
|
sqlite_active = True
|
2025-11-17 12:04:17 +01:00
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
if sqlite_active:
|
|
|
|
|
logger.debug(f"SQLITE: using local database file at {sqlite_file} ...")
|
|
|
|
|
if not os.path.exists(sqlite_file):
|
|
|
|
|
db_create()
|
2025-11-17 12:04:17 +01:00
|
|
|
|
|
|
|
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
|
|
|
logger.info('LOGLEVEL is set to DEBUG')
|
|
|
|
|
|
|
|
|
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
|
server.bind((client_host,client_port))
|
2025-12-20 15:41:59 +01:00
|
|
|
#logger.info(f"CLIENT: listening at {client_host}:{client_port} ...")
|
2025-11-17 12:04:17 +01:00
|
|
|
server.listen()
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
if sqlite_active:
|
|
|
|
|
server2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
server2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
|
server2.bind((forward_host,forward_port))
|
|
|
|
|
server2.listen()
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
device_handle_thread = threading.Thread(target=device_handle, name="DEVICE_HANDLE")
|
|
|
|
|
device_handle_thread.start()
|
|
|
|
|
threads.append(device_handle_thread)
|
|
|
|
|
|
2025-12-20 15:41:59 +01:00
|
|
|
if sqlite_active:
|
|
|
|
|
logger.debug(f"CLIENT: waiting for forward-clients to connect ...")
|
|
|
|
|
forward_handle_thread = threading.Thread(target=client_connect, args=(server2,True,), kwargs={}, name="FORWARD_HANDLE")
|
|
|
|
|
threads.append(forward_handle_thread)
|
|
|
|
|
forward_handle_thread.start()
|
|
|
|
|
|
2025-11-17 12:04:17 +01:00
|
|
|
logger.debug(f"CLIENT: waiting for clients to connect ...")
|
2025-12-20 15:41:59 +01:00
|
|
|
client_connect(server)
|