paqctl v1.0.0 - Unified proxy manager for bypassing firewalls
Features: - Dual backend support: paqet (KCP) and GFW-knocker (violated TCP + QUIC) - Both backends can run simultaneously when both are installed - Automatic config.yaml generation for paqet backend - Windows client support with PowerShell script - Telegram monitoring integration - Systemd service management Backends: - paqet: Single Go binary with built-in SOCKS5 (port 1080) - GFW-knocker: Python-based with violated TCP tunneling (port 14000)
This commit is contained in:
36
gfk/server/mainserver.py
Normal file
36
gfk/server/mainserver.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
import signal
|
||||
|
||||
|
||||
scripts = ['quic_server.py', 'vio_server.py']
|
||||
|
||||
|
||||
def run_script(script_name):
|
||||
# Use sys.executable to run with the same Python interpreter (venv)
|
||||
os.system(f"pkill -f {script_name}")
|
||||
time.sleep(0.5)
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
return p
|
||||
|
||||
|
||||
processes = []
|
||||
def signal_handler(sig, frame):
|
||||
print('You pressed Ctrl+C!')
|
||||
for p in processes:
|
||||
print("terminated:",p)
|
||||
p.terminate()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
p1 = run_script(scripts[0])
|
||||
time.sleep(1)
|
||||
p2 = run_script(scripts[1])
|
||||
processes.extend([p1, p2]) # Modify global list, don't shadow it
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
p1.wait()
|
||||
p2.wait()
|
||||
print("All subprocesses have completed.")
|
||||
310
gfk/server/quic_server.py
Normal file
310
gfk/server/quic_server.py
Normal file
@@ -0,0 +1,310 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
from aioquic.asyncio import QuicConnectionProtocol, serve
|
||||
from aioquic.quic.configuration import QuicConfiguration
|
||||
from aioquic.quic.events import ConnectionTerminated, StreamDataReceived, StreamReset
|
||||
import parameters
|
||||
|
||||
# Setup basic logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("QuicServer")
|
||||
|
||||
# Global list to track active protocol instances
|
||||
active_protocols = []
|
||||
|
||||
class TunnelServerProtocol(QuicConnectionProtocol):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.tcp_connections = {} # Map TCP connections to QUIC streams
|
||||
self.udp_connections = {} # Map UDP connections to QUIC streams
|
||||
self.udp_last_activity = {} # Track last activity time for UDP connections
|
||||
active_protocols.append(self) # Add this protocol instance to the list
|
||||
try:
|
||||
asyncio.create_task(self.cleanup_stale_udp_connections())
|
||||
except Exception as e:
|
||||
logger.info(f"Error in cleanup_stale_udp task: {e}")
|
||||
|
||||
def connection_lost(self, exc):
|
||||
logger.info("Quic channel lost")
|
||||
if self in active_protocols:
|
||||
active_protocols.remove(self)
|
||||
super().connection_lost(exc)
|
||||
self.close_all_tcp_connections()
|
||||
self.close_all_udp_connections()
|
||||
|
||||
def close_all_tcp_connections(self):
|
||||
logger.info("Closing all TCP connections from server...")
|
||||
for stream_id, (reader, writer) in self.tcp_connections.items():
|
||||
logger.info(f"Closing TCP connection for stream {stream_id}...")
|
||||
writer.close()
|
||||
self.tcp_connections.clear()
|
||||
|
||||
def close_all_udp_connections(self):
|
||||
logger.info("Closing all UDP connections from server...")
|
||||
for stream_id, (transport, _) in self.udp_connections.items():
|
||||
logger.info(f"Closing UDP connection for stream {stream_id}...")
|
||||
transport.close()
|
||||
self.udp_connections.clear()
|
||||
self.udp_last_activity.clear()
|
||||
|
||||
|
||||
def close_this_stream(self, stream_id):
|
||||
try:
|
||||
logger.info(f"FIN to stream={stream_id} sent")
|
||||
self._quic.send_stream_data(stream_id, b"", end_stream=True) # Send FIN flag
|
||||
self.transmit() # Send the FIN flag over the network
|
||||
except Exception as e:
|
||||
logger.info(f"Error closing stream at server: {e}")
|
||||
|
||||
try:
|
||||
if stream_id in self.tcp_connections:
|
||||
writer = self.tcp_connections[stream_id][1]
|
||||
writer.close()
|
||||
del self.tcp_connections[stream_id]
|
||||
if stream_id in self.udp_connections:
|
||||
transport, _ = self.udp_connections[stream_id]
|
||||
transport.close()
|
||||
del self.udp_connections[stream_id]
|
||||
del self.udp_last_activity[stream_id]
|
||||
except Exception as e:
|
||||
logger.info(f"Error closing socket at server: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
async def cleanup_stale_udp_connections(self):
|
||||
logger.info("UDP cleanup task running!")
|
||||
check_time = min(parameters.udp_timeout,60)
|
||||
while True:
|
||||
await asyncio.sleep(check_time) # Run cleanup every 60 seconds
|
||||
current_time = self.loop.time()
|
||||
stale_streams = [
|
||||
stream_id for stream_id, last_time in self.udp_last_activity.items()
|
||||
if current_time - last_time > parameters.udp_timeout
|
||||
]
|
||||
for stream_id in stale_streams:
|
||||
logger.info(f"idle UDP stream={stream_id} timeout reached")
|
||||
self.close_this_stream(stream_id)
|
||||
|
||||
|
||||
|
||||
async def forward_tcp_to_quic(self, stream_id, reader):
|
||||
logger.info(f"Task TCP to QUIC started")
|
||||
try:
|
||||
while True:
|
||||
data = await reader.read(4096) # Read data from TCP socket
|
||||
if not data:
|
||||
break
|
||||
# logger.info(f"Forwarding data from TCP to QUIC on stream {stream_id}")
|
||||
self._quic.send_stream_data(stream_id=stream_id, data=data, end_stream=False)
|
||||
self.transmit() # Flush
|
||||
except Exception as e:
|
||||
logger.info(f"Error forwarding TCP to QUIC: {e}")
|
||||
finally:
|
||||
logger.info(f"Task TCP to QUIC Ended")
|
||||
self.close_this_stream(stream_id)
|
||||
|
||||
|
||||
|
||||
async def connect_tcp(self, stream_id, target_port):
|
||||
logger.info(f"Connecting to TCP:{target_port}...")
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(parameters.xray_server_ip_address, target_port)
|
||||
logger.info(f"TCP connection established for stream {stream_id} to port {target_port}")
|
||||
|
||||
# Start forwarding data from TCP to QUIC
|
||||
asyncio.create_task(self.forward_tcp_to_quic(stream_id, reader))
|
||||
|
||||
resp_data = parameters.quic_auth_code + "i am ready,!###!"
|
||||
self._quic.send_stream_data(stream_id=stream_id, data=resp_data.encode("utf-8"), end_stream=False)
|
||||
self.transmit() # Flush
|
||||
|
||||
self.tcp_connections[stream_id] = (reader, writer)
|
||||
except Exception as e:
|
||||
logger.info(f"Failed to establish TCP:{target_port} connection: {e}")
|
||||
self.close_this_stream(stream_id)
|
||||
|
||||
|
||||
|
||||
async def forward_udp_to_quic(self, stream_id, protocol):
|
||||
logger.info(f"Task UDP to QUIC started")
|
||||
try:
|
||||
while True:
|
||||
data, _ = await protocol.queue.get() # Wait for data from UDP
|
||||
if(data == None):
|
||||
break
|
||||
# logger.info(f"Forwarding data from UDP to QUIC on stream {stream_id}")
|
||||
self._quic.send_stream_data(stream_id, data)
|
||||
self.transmit() # Flush
|
||||
self.udp_last_activity[stream_id] = self.loop.time()
|
||||
except Exception as e:
|
||||
logger.info(f"Error forwarding UDP to QUIC: {e}")
|
||||
finally:
|
||||
logger.info(f"Task UDP to QUIC Ended")
|
||||
self.close_this_stream(stream_id)
|
||||
|
||||
|
||||
async def connect_udp(self, stream_id, target_port):
|
||||
class UdpProtocol:
|
||||
def __init__(self):
|
||||
self.transport = None
|
||||
self.queue = asyncio.Queue()
|
||||
self.stream_id = stream_id
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
def datagram_received(self, data, addr):
|
||||
logger.info(f"put this to queue data={data} addr={addr}")
|
||||
self.queue.put_nowait((data, addr))
|
||||
|
||||
def error_received(self, exc):
|
||||
logger.info(f"UDP error received: {exc}")
|
||||
self.queue.put_nowait((None, None)) # to cancel task
|
||||
if(self.transport):
|
||||
self.transport.close()
|
||||
logger.info("UDP transport closed")
|
||||
|
||||
def connection_lost(self, exc):
|
||||
logger.info("UDP connection lost.")
|
||||
self.queue.put_nowait((None, None)) # to cancel task
|
||||
if(self.transport):
|
||||
self.transport.close()
|
||||
logger.info("UDP transport closed")
|
||||
|
||||
try:
|
||||
# Create a UDP socket
|
||||
logger.info(f"Connecting to UDP:{target_port}...")
|
||||
loop = asyncio.get_event_loop()
|
||||
transport, protocol = await loop.create_datagram_endpoint(
|
||||
UdpProtocol,
|
||||
remote_addr=(parameters.xray_server_ip_address, target_port)
|
||||
)
|
||||
self.udp_connections[stream_id] = (transport, protocol)
|
||||
self.udp_last_activity[stream_id] = self.loop.time() # Track last activity time
|
||||
logger.info(f"UDP connection established for stream {stream_id} to port {target_port}")
|
||||
|
||||
asyncio.create_task(self.forward_udp_to_quic(stream_id, protocol))
|
||||
except Exception as e:
|
||||
logger.info(f"Failed to establish UDP connection: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
def quic_event_received(self, event):
|
||||
# print("EVENT",event)
|
||||
if isinstance(event, StreamDataReceived):
|
||||
try:
|
||||
# logger.info(f"Server received from QUIC on stream {event.stream_id}")
|
||||
# logger.info(f"Server TCP IDs -> {self.tcp_connections.keys()}")
|
||||
# logger.info(f"Server UDP IDs -> {self.udp_connections.keys()}")
|
||||
|
||||
if event.end_stream:
|
||||
logger.info(f"Stream={event.stream_id} closed by client.")
|
||||
self.close_this_stream(event.stream_id)
|
||||
|
||||
# Forward data to the corresponding TCP connection
|
||||
elif event.stream_id in self.tcp_connections:
|
||||
writer = self.tcp_connections[event.stream_id][1]
|
||||
writer.write(event.data) # Send data over TCP
|
||||
try:
|
||||
asyncio.create_task(writer.drain())
|
||||
except ConnectionResetError as e42:
|
||||
logger.info(f"ERR in writer drain task : {e42}")
|
||||
except Exception as e43:
|
||||
logger.info(f"ERR in writer drain task : {e43}")
|
||||
|
||||
# Forward data to the corresponding UDP connection
|
||||
elif event.stream_id in self.udp_connections:
|
||||
transport, _ = self.udp_connections[event.stream_id]
|
||||
transport.sendto(event.data) # Send data over UDP
|
||||
self.udp_last_activity[event.stream_id] = self.loop.time() # Update last activity time
|
||||
|
||||
else:
|
||||
socket_type = None
|
||||
socket_port = 0
|
||||
|
||||
# Assume req is like => auth+"connect,udp,443,!###!"
|
||||
new_req = event.data.split(b",!###!", 1)
|
||||
req_header = ""
|
||||
try:
|
||||
req_header = new_req[0].decode("utf-8")
|
||||
except Exception as e47:
|
||||
logger.info(f"ERR in req decoding : {e47}")
|
||||
req_header=""
|
||||
|
||||
logger.info("New req comes -> " + req_header)
|
||||
|
||||
if req_header.startswith(parameters.quic_auth_code + "connect,"):
|
||||
j = len(parameters.quic_auth_code) + 8
|
||||
if req_header[j:j + 3] == "tcp":
|
||||
socket_type = "tcp"
|
||||
elif req_header[j:j + 3] == "udp":
|
||||
socket_type = "udp"
|
||||
|
||||
try:
|
||||
socket_port = int(req_header[j + 4:])
|
||||
except ValueError:
|
||||
logger.info("Invalid port.")
|
||||
|
||||
if socket_port > 0:
|
||||
if socket_type == "tcp":
|
||||
# New stream detected, create a TCP connection
|
||||
asyncio.create_task(self.connect_tcp(event.stream_id, socket_port))
|
||||
elif socket_type == "udp":
|
||||
# New stream detected, create a UDP connection
|
||||
asyncio.create_task(self.connect_udp(event.stream_id, socket_port))
|
||||
else:
|
||||
logger.info("Invalid Req: socket type unknown.")
|
||||
else:
|
||||
logger.info("Invalid Req: socket port unknown.")
|
||||
else:
|
||||
logger.info("Invalid Req header")
|
||||
|
||||
except Exception as e:
|
||||
logger.info(f"Quic event server error: {e}")
|
||||
|
||||
elif isinstance(event, StreamReset):
|
||||
# Handle stream reset (client closed the stream)
|
||||
logger.info(f"Stream {event.stream_id} reset by client.")
|
||||
self.close_this_stream(event.stream_id)
|
||||
|
||||
elif isinstance(event, ConnectionTerminated):
|
||||
logger.info(f"Connection lost: {event.reason_phrase}")
|
||||
self.connection_lost(event.reason_phrase)
|
||||
|
||||
|
||||
async def run_server():
|
||||
configuration = QuicConfiguration(is_client=False)
|
||||
configuration.load_cert_chain(parameters.quic_cert_filepath[0], parameters.quic_cert_filepath[1])
|
||||
configuration.max_data = parameters.quic_max_data
|
||||
configuration.max_stream_data = parameters.quic_max_stream_data
|
||||
configuration.idle_timeout = parameters.quic_idle_timeout
|
||||
configuration.max_datagram_size = parameters.quic_mtu
|
||||
|
||||
# Start QUIC server
|
||||
await serve("0.0.0.0", parameters.quic_server_port, configuration=configuration, create_protocol=TunnelServerProtocol)
|
||||
logger.warning(f"Server listening for QUIC on port {parameters.quic_server_port}")
|
||||
|
||||
# Keep the server running
|
||||
await asyncio.Future() # Run forever
|
||||
|
||||
|
||||
def handle_shutdown(signum, frame):
|
||||
logger.info("Shutting down server gracefully...")
|
||||
for protocol in active_protocols:
|
||||
protocol.close_all_tcp_connections()
|
||||
protocol.close_all_udp_connections()
|
||||
protocol.close()
|
||||
logger.info("Server shutdown complete.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGTERM, handle_shutdown)
|
||||
signal.signal(signal.SIGINT, handle_shutdown)
|
||||
|
||||
asyncio.run(run_server())
|
||||
230
gfk/server/vio_server.py
Normal file
230
gfk/server/vio_server.py
Normal file
@@ -0,0 +1,230 @@
|
||||
from scapy.all import AsyncSniffer,IP,TCP,Raw,conf
|
||||
import asyncio
|
||||
import random
|
||||
import parameters
|
||||
import logging
|
||||
import time
|
||||
|
||||
|
||||
|
||||
# Setup basic logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("VioServer")
|
||||
|
||||
|
||||
vps_ip = parameters.vps_ip
|
||||
|
||||
vio_tcp_server_port = parameters.vio_tcp_server_port
|
||||
vio_udp_server_port = parameters.vio_udp_server_port
|
||||
quic_local_ip = parameters.quic_local_ip
|
||||
quic_server_port = parameters.quic_server_port
|
||||
|
||||
|
||||
|
||||
global client_ip # obtained during sniffing
|
||||
global client_port # obtained during sniffing
|
||||
|
||||
client_ip = "1.1.1.1"
|
||||
client_port = 443
|
||||
|
||||
tcp_options=[
|
||||
('MSS', 1280), # Maximum Segment Size
|
||||
('WScale', 8), # Window Scale
|
||||
('SAckOK', ''), # Selective ACK Permitted
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
async def async_sniff_realtime(qu1):
|
||||
logger.info("sniffer started")
|
||||
try:
|
||||
def process_packet(packet):
|
||||
# logger.info(f"sniffed before if at {time.time()}")
|
||||
# Check flags using 'in' to handle different flag orderings (AP vs PA)
|
||||
flags = str(packet[TCP].flags) if packet.haslayer(TCP) else ''
|
||||
if packet.haslayer(TCP) and packet[TCP].dport == vio_tcp_server_port and 'A' in flags and 'P' in flags:
|
||||
data1 = packet[TCP].load
|
||||
client_ip = packet[IP].src
|
||||
client_port = packet[TCP].sport
|
||||
qu1.put_nowait( (data1,client_ip,client_port) )
|
||||
# logger.info(f"sniffed on tcp : {client_ip} {client_port} at {time.time()}")
|
||||
|
||||
|
||||
async def start_sniffer():
|
||||
sniffer = AsyncSniffer(prn=process_packet,
|
||||
filter=f"tcp and dst host {vps_ip} and dst port {vio_tcp_server_port}",
|
||||
store=False)
|
||||
sniffer.start()
|
||||
return sniffer
|
||||
|
||||
sniffer = await start_sniffer()
|
||||
return sniffer
|
||||
except Exception as e:
|
||||
logger.info(f"sniff Generic error: {e}....")
|
||||
|
||||
|
||||
|
||||
|
||||
async def forward_vio_to_quic(qu1, transport):
|
||||
global client_ip
|
||||
global client_port
|
||||
logger.info(f"Task vio to Quic started")
|
||||
addr = (quic_local_ip,quic_server_port)
|
||||
# addr = ("192.168.1.140",quic_server_port)
|
||||
try:
|
||||
while True:
|
||||
# update client_ip, client_port from the queue
|
||||
data,client_ip,client_port = await qu1.get()
|
||||
# logger.info(f"data qu1 fetched {data} at {time.time()}")
|
||||
if(data == None):
|
||||
break
|
||||
transport.sendto(data , addr)
|
||||
# logger.info(f"data sent to udp {data} -> {addr} at {time.time()}")
|
||||
# qu1.task_done()
|
||||
except Exception as e:
|
||||
logger.info(f"Error forwarding vio to Quic: {e}")
|
||||
finally:
|
||||
logger.info(f"Task vio to Quic Ended.")
|
||||
|
||||
|
||||
|
||||
basepkt = IP() / TCP(sport=vio_tcp_server_port, seq=1, flags="AP", ack=0, options=tcp_options) / Raw(load=b"")
|
||||
|
||||
skt = conf.L3socket()
|
||||
|
||||
def send_to_violated_TCP(binary_data,client_ip,client_port):
|
||||
# logger.info(f"client ip = {client_ip}")
|
||||
new_pkt = basepkt.copy()
|
||||
new_pkt[IP].dst = client_ip
|
||||
new_pkt[TCP].dport = client_port
|
||||
# new_pkt[TCP].seq = random.randint(1024,1048576)
|
||||
# new_pkt[TCP].ack = random.randint(1024,1048576)
|
||||
new_pkt[TCP].load = binary_data
|
||||
skt.send(new_pkt)
|
||||
|
||||
|
||||
|
||||
|
||||
async def forward_quic_to_vio(protocol):
|
||||
logger.info(f"Task QUIC to vio started")
|
||||
global client_ip
|
||||
global client_port
|
||||
try:
|
||||
while True:
|
||||
data = await protocol.queue.get() # Wait for data from UDP
|
||||
if(data == None):
|
||||
break
|
||||
send_to_violated_TCP(data,client_ip,client_port)
|
||||
# logger.info(f"data send to tcp {data} at {time.time()}")
|
||||
except Exception as e:
|
||||
logger.info(f"Error forwarding QUIC to vio: {e}")
|
||||
finally:
|
||||
logger.info(f"Task QUIC to vio Ended.")
|
||||
|
||||
|
||||
|
||||
|
||||
async def start_udp_server(qu1):
|
||||
while True:
|
||||
try:
|
||||
logger.warning(f"violated tcp:{vio_tcp_server_port} -> quic {quic_local_ip}:{quic_server_port} -> ")
|
||||
loop = asyncio.get_event_loop()
|
||||
transport, udp_protocol = await loop.create_datagram_endpoint(
|
||||
lambda: UdpProtocol(),
|
||||
local_addr=("0.0.0.0", vio_udp_server_port),
|
||||
remote_addr=(quic_local_ip, quic_server_port)
|
||||
# remote_addr=("192.168.1.140", quic_server_port)
|
||||
)
|
||||
|
||||
task1 = asyncio.create_task(forward_quic_to_vio(udp_protocol))
|
||||
task2 = asyncio.create_task(forward_vio_to_quic(qu1,transport))
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(0.02) # this make async loop to switch better between process
|
||||
if(udp_protocol.has_error):
|
||||
task1.cancel()
|
||||
task2.cancel()
|
||||
await asyncio.sleep(1)
|
||||
logger.info(f"all task cancelled")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.info(f"vioServer ERR: {e}")
|
||||
finally:
|
||||
transport.close()
|
||||
await asyncio.sleep(0.5)
|
||||
transport.abort()
|
||||
logger.info("aborting transport ...")
|
||||
await asyncio.sleep(1.5)
|
||||
logger.info("vio inner finished")
|
||||
|
||||
|
||||
|
||||
|
||||
class UdpProtocol:
|
||||
def __init__(self):
|
||||
self.transport = None
|
||||
self.has_error = False
|
||||
self.queue = asyncio.Queue()
|
||||
|
||||
def connection_made(self, transport):
|
||||
logger.info("NEW DGRAM socket created")
|
||||
logger.info(transport.get_extra_info('socket'))
|
||||
self.transport = transport
|
||||
|
||||
def pause_writing(self):
|
||||
pass # UDP doesn't need flow control, but we had to implement
|
||||
|
||||
def resume_writing(self):
|
||||
pass # UDP doesn't need flow control, but we had to implement
|
||||
|
||||
def datagram_received(self, data, addr):
|
||||
self.queue.put_nowait(data)
|
||||
# logger.info(f"data received from udp {data} at {time.time()}")
|
||||
|
||||
def error_received(self, exc):
|
||||
logger.info(f"UDP error received: {exc}")
|
||||
self.has_error = True
|
||||
if(self.transport):
|
||||
self.transport.close()
|
||||
logger.info("UDP transport closed")
|
||||
|
||||
def connection_lost(self, exc):
|
||||
logger.info(f"UDP lost. {exc}")
|
||||
self.has_error = True
|
||||
if(self.transport):
|
||||
self.transport.close()
|
||||
logger.info("UDP transport closed")
|
||||
|
||||
|
||||
|
||||
|
||||
async def run_vio_server():
|
||||
sniffer = None
|
||||
try:
|
||||
qu1 = asyncio.Queue()
|
||||
sniffer = await async_sniff_realtime(qu1)
|
||||
|
||||
await asyncio.gather(
|
||||
start_udp_server(qu1),
|
||||
return_exceptions=True
|
||||
)
|
||||
|
||||
logger.info("end ?")
|
||||
except SystemExit as e:
|
||||
logger.info(f"Caught SystemExit: {e}")
|
||||
except asyncio.CancelledError as e:
|
||||
logger.info(f"cancelling error: {e}")
|
||||
except ConnectionError as e:
|
||||
logger.info(f"Connection error: {e}")
|
||||
except Exception as e:
|
||||
logger.info(f"Generic error: {e}")
|
||||
finally:
|
||||
if sniffer is not None:
|
||||
sniffer.stop()
|
||||
logger.info("stop sniffer")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_vio_server())
|
||||
Reference in New Issue
Block a user