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:
SamNet-dev
2026-02-04 00:05:04 -06:00
commit 975acc4cf5
13 changed files with 10042 additions and 0 deletions

38
gfk/client/mainclient.py Normal file
View File

@@ -0,0 +1,38 @@
import subprocess
import os
import time
import sys
import signal
scripts = ['quic_client.py', 'vio_client.py']
def run_script(script_name):
# Use sys.executable to run with the same Python interpreter (venv)
subprocess.run(['pkill', '-f', script_name], stderr=subprocess.DEVNULL)
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.")

380
gfk/client/quic_client.py Normal file
View File

@@ -0,0 +1,380 @@
import asyncio
import logging
import sys
import time
import multiprocessing
from aioquic.asyncio import QuicConnectionProtocol, connect
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import ConnectionTerminated, StreamDataReceived, StreamReset
import parameters
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("QuicClient")
active_protocols = []
is_quic_established = False
class TunnelClientProtocol(QuicConnectionProtocol):
def __init__(self, *args, **kwargs):
global is_quic_established
is_quic_established = False
super().__init__(*args, **kwargs)
self.loop = asyncio.get_event_loop()
self.tcp_connections = {}
self.tcp_syn_wait = {}
self.udp_addr_to_stream = {}
self.udp_stream_to_addr = {}
self.udp_stream_to_transport = {}
self.udp_last_activity = {}
active_protocols.append(self)
asyncio.create_task(self.cleanup_stale_udp_connections())
asyncio.create_task(self.check_start_connectivity())
async def check_start_connectivity(self):
global is_quic_established
try:
await asyncio.sleep(7)
if is_quic_established:
logger.info(f"Quic Established!")
else:
logger.info(f"Quic FAILED to connect")
self.connection_lost("quic connectivity")
except SystemExit as e:
logger.info(f"connectivity SystemExit: {e}")
except Exception as e:
logger.info(f"connectivity err: {e}")
def connection_lost(self, exc):
super().connection_lost(exc)
self.close_all_tcp_connections()
logger.info("QUIC connection lost. exit")
for protocol in active_protocols:
protocol.close_all_tcp_connections()
protocol.close_all_udp_connections()
protocol.close()
protocol = None
if self in active_protocols:
active_protocols.remove(self)
time.sleep(1)
sys.exit()
def close_all_tcp_connections(self):
logger.info("close all tcp")
for stream_id, (reader, writer) in self.tcp_connections.items():
logger.info(f"Closing TCP connection for stream {stream_id}...")
try:
writer.close()
except Exception as e:
logger.info(f"Error closing tcp socket: {e}")
for stream_id, (reader, writer) in self.tcp_syn_wait.items():
logger.info(f"Closing TCP connection for stream {stream_id}...")
try:
writer.close()
except Exception as e:
logger.info(f"Error closing tcp socket: {e}")
self.tcp_connections.clear()
self.tcp_syn_wait.clear()
def close_all_udp_connections(self):
logger.info("close all udp")
self.udp_addr_to_stream.clear()
self.udp_stream_to_addr.clear()
self.udp_last_activity.clear()
self.udp_stream_to_transport.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)
self.transmit()
except Exception as e:
logger.info(f"Error closing stream at client: {e}")
try:
if stream_id in self.tcp_connections:
try:
writer = self.tcp_connections[stream_id][1]
writer.close()
del self.tcp_connections[stream_id]
except Exception as e:
logger.info(f"Error closing tcp estblsh at client: {e}")
if stream_id in self.tcp_syn_wait:
try:
writer = self.tcp_syn_wait[stream_id][1]
writer.close()
del self.tcp_syn_wait[stream_id]
except Exception as e:
logger.info(f"Error closing tcp syn at client: {e}")
if stream_id in self.udp_stream_to_addr:
try:
addr = self.udp_stream_to_addr.get(stream_id)
del self.udp_addr_to_stream[addr]
del self.udp_stream_to_addr[stream_id]
del self.udp_last_activity[stream_id]
del self.udp_stream_to_transport[stream_id]
except Exception as e:
logger.info(f"Error closing udp at client: {e}")
except Exception as e:
logger.info(f"Error closing socket at client: {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)
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):
logger.info(f"Task TCP to QUIC started")
try:
(reader, writer) = self.tcp_syn_wait[stream_id]
self.tcp_connections[stream_id] = (reader, writer)
del self.tcp_syn_wait[stream_id]
while True:
data = await reader.read(4096)
if not data:
break
self._quic.send_stream_data(stream_id=stream_id, data=data, end_stream=False)
self.transmit()
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 handle_tcp_connection(self, reader, writer, target_port):
stream_id = None
try:
stream_id = self._quic.get_next_available_stream_id()
self.tcp_syn_wait[stream_id] = (reader, writer)
req_data = parameters.quic_auth_code + "connect,tcp," + str(target_port) + ",!###!"
self._quic.send_stream_data(stream_id=stream_id, data=req_data.encode("utf-8"), end_stream=False)
self.transmit()
except Exception as e:
logger.info(f"Client Error handle tcp connection: {e}")
if stream_id is not None:
self.close_this_stream(stream_id)
async def forward_udp_to_quic(self, udp_protocol):
logger.info("Task UDP to Quic started")
stream_id = None
try:
while True:
data, addr = await udp_protocol.queue.get()
stream_id = self.udp_addr_to_stream.get(addr)
if stream_id is not None:
self._quic.send_stream_data(stream_id=stream_id, data=data, end_stream=False)
self.transmit()
self.udp_last_activity[stream_id] = self.loop.time()
else:
stream_id = self.new_udp_stream(addr, udp_protocol)
if stream_id is not None:
await asyncio.sleep(0.1)
self.udp_last_activity[stream_id] = self.loop.time()
self._quic.send_stream_data(stream_id=stream_id, data=data, end_stream=False)
self.transmit()
except Exception as e:
logger.info(f"Error forwarding UDP to QUIC: {e}")
finally:
logger.info(f"Task UDP to QUIC Ended")
if stream_id is not None:
self.close_this_stream(stream_id)
def new_udp_stream(self, addr, udp_protocol):
logger.info(f"new stream for UDP addr {addr} -> {udp_protocol.target_port}")
try:
stream_id = self._quic.get_next_available_stream_id()
self.udp_addr_to_stream[addr] = stream_id
self.udp_stream_to_addr[stream_id] = addr
self.udp_stream_to_transport[stream_id] = udp_protocol.transport
self.udp_last_activity[stream_id] = self.loop.time()
req_data = parameters.quic_auth_code + "connect,udp," + str(udp_protocol.target_port) + ",!###!"
self._quic.send_stream_data(stream_id=stream_id, data=req_data.encode("utf-8"), end_stream=False)
self.transmit()
return stream_id
except Exception as e:
logger.info(f"Client Error creating new udp stream: {e}")
return None
def quic_event_received(self, event):
if isinstance(event, StreamDataReceived):
try:
if event.end_stream:
logger.info(f"Stream={event.stream_id} closed by server.")
self.close_this_stream(event.stream_id)
elif event.stream_id in self.tcp_connections:
writer = self.tcp_connections[event.stream_id][1]
writer.write(event.data)
asyncio.create_task(writer.drain())
elif event.stream_id in self.udp_stream_to_addr:
addr = self.udp_stream_to_addr[event.stream_id]
transport = self.udp_stream_to_transport[event.stream_id]
transport.sendto(event.data, addr)
elif event.stream_id in self.tcp_syn_wait:
if event.data == (parameters.quic_auth_code + "i am ready,!###!").encode("utf-8"):
asyncio.create_task(self.forward_tcp_to_quic(event.stream_id))
else:
logger.warning("unknown Data arrived to client")
except Exception as e:
logger.info(f"Quic event client error: {e}")
elif isinstance(event, StreamReset):
logger.info(f"Stream {event.stream_id} reset unexpectedly.")
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_client():
global is_quic_established
configuration = QuicConfiguration(is_client=True)
configuration.verify_mode = parameters.quic_verify_cert
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
try:
logger.warning("Attempting to connect to QUIC server...")
async with connect(parameters.quic_local_ip,
parameters.vio_udp_client_port,
configuration=configuration,
create_protocol=TunnelClientProtocol,
local_port=parameters.quic_client_port) as client:
async def start_tcp_server(local_port, target_port):
logger.warning(f"client listen tcp:{local_port} -> to server tcp:{target_port}")
server = await asyncio.start_server(
lambda r, w: asyncio.create_task(handle_tcp_client(r, w, target_port)),
'0.0.0.0', local_port
)
async with server:
await server.serve_forever()
logger.info("tcp server finished")
async def handle_tcp_client(reader, writer, target_port):
while not active_protocols:
logger.info("Waiting for an active QUIC connection...")
await asyncio.sleep(1)
protocol = active_protocols[-1]
await protocol.handle_tcp_connection(reader, writer, target_port)
async def start_udp_server(local_port, target_port):
while True:
try:
logger.warning(f"client listen udp:{local_port} -> to server udp:{target_port}")
loop = asyncio.get_event_loop()
transport, udp_protocol = await loop.create_datagram_endpoint(
lambda: UdpProtocol(client, target_port),
local_addr=('0.0.0.0', local_port)
)
mytask = asyncio.create_task(handle_udp_client(udp_protocol))
while True:
await asyncio.sleep(0.05)
if udp_protocol.has_error:
mytask.cancel()
await asyncio.sleep(1)
break
logger.info(f"udp server finished")
except Exception as e:
logger.info(f"start_udp_server ERR: {e}")
async def handle_udp_client(udp_protocol):
logger.info("creating udp task ....")
while not active_protocols:
logger.info("Waiting for an active QUIC connection...")
await asyncio.sleep(1)
protocol = active_protocols[-1]
await protocol.forward_udp_to_quic(udp_protocol)
class UdpProtocol:
def __init__(self, client, target_port):
self.transport = None
self.client = client
self.target_port = target_port
self.has_error = False
self.queue = asyncio.Queue()
def connection_made(self, transport):
logger.info("NEW DGRAM listen created")
logger.info(transport.get_extra_info('socket'))
self.transport = transport
def datagram_received(self, data, addr):
self.queue.put_nowait((data, addr))
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")
is_quic_established = True
tcp_servers_list = []
for lport, tport in parameters.tcp_port_mapping.items():
tcp_servers_list.append(start_tcp_server(lport, tport))
udp_servers_list = []
for lport, tport in parameters.udp_port_mapping.items():
udp_servers_list.append(start_udp_server(lport, tport))
await asyncio.gather(
asyncio.Future(),
*tcp_servers_list,
*udp_servers_list
)
except SystemExit as e:
logger.info(f"Caught SystemExit: {e}")
except asyncio.CancelledError as e:
logger.info(f"cancelling error: {e}. Retrying...")
except ConnectionError as e:
logger.info(f"Connection error: {e}. Retrying...")
except Exception as e:
logger.info(f"Generic error: {e}. Retrying...")
def Quic_client():
asyncio.run(run_client())
if __name__ == "__main__":
while True:
process = multiprocessing.Process(target=Quic_client)
process.start()
while process.is_alive():
time.sleep(5)
logger.info("client is dead. restarting ...")
time.sleep(1)

198
gfk/client/vio_client.py Normal file
View File

@@ -0,0 +1,198 @@
from scapy.all import AsyncSniffer,IP,TCP,Raw,conf,Ether,get_if_hwaddr
import asyncio
import parameters
import logging
import os
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("VioClient")
vps_ip = parameters.vps_ip
vio_tcp_server_port = parameters.vio_tcp_server_port
vio_tcp_client_port = parameters.vio_tcp_client_port
vio_udp_client_port = parameters.vio_udp_client_port
quic_local_ip = parameters.quic_local_ip
quic_client_port = parameters.quic_client_port
# Windows-specific: get local IP and gateway MAC for Ethernet frames
my_ip = getattr(parameters, 'my_ip', None)
gateway_mac = getattr(parameters, 'gateway_mac', None)
is_windows = os.name == 'nt'
try:
local_mac = get_if_hwaddr(conf.iface)
except Exception:
local_mac = None
tcp_options=[
('MSS', 1280),
('WScale', 8),
('SAckOK', ''),
]
async def async_sniff_realtime(qu1):
logger.info("sniffer started")
try:
def process_packet(packet):
# 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[IP].src == vps_ip and packet[TCP].sport == vio_tcp_server_port and 'A' in flags and 'P' in flags:
data1 = packet[TCP].load
qu1.put_nowait(data1)
async def start_sniffer():
sniffer = AsyncSniffer(prn=process_packet,
filter=f"tcp and src host {vps_ip} and src 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}....")
raise # Re-raise so caller knows sniffer failed
async def forward_vio_to_quic(qu1, transport):
logger.info(f"Task vio to Quic started")
addr = (quic_local_ip, quic_client_port)
try:
while True:
data = await qu1.get()
if data == None:
break
transport.sendto(data, addr)
except Exception as e:
logger.info(f"Error forwarding vio to Quic: {e}")
finally:
logger.info(f"Task vio to Quic Ended.")
# Build base packet based on OS
if is_windows and gateway_mac and my_ip and local_mac:
logger.info(f"Windows mode: using Ethernet frames (gw_mac={gateway_mac}, my_ip={my_ip})")
basepkt = Ether(dst=gateway_mac, src=local_mac) / IP(src=my_ip, dst=vps_ip) / TCP(sport=vio_tcp_client_port, dport=vio_tcp_server_port, seq=0, flags="AP", ack=0, options=tcp_options) / Raw(load=b"")
skt = conf.L2socket(iface=conf.iface)
else:
logger.info(f"Linux mode: using L3 socket")
basepkt = IP(dst=vps_ip) / TCP(sport=vio_tcp_client_port, dport=vio_tcp_server_port, seq=0, flags="AP", ack=0, options=tcp_options) / Raw(load=b"")
skt = conf.L3socket()
def send_to_violated_TCP(binary_data):
new_pkt = basepkt.copy()
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")
try:
while True:
data = await protocol.queue.get()
if data == None:
break
send_to_violated_TCP(data)
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"listen quic:{vio_udp_client_port} -> violated tcp:{vio_tcp_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_client_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)
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"vioclient 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 listen created")
logger.info(transport.get_extra_info('socket'))
self.transport = transport
def pause_writing(self):
pass
def resume_writing(self):
pass
def datagram_received(self, data, addr):
self.queue.put_nowait(data)
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_client():
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_client())

36
gfk/server/mainserver.py Normal file
View 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
View 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
View 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())