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

28
.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/
.venv/
# Client config (contains credentials)
gfk/parameters.py
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Release binaries (uploaded to GitHub releases, not committed)
*.tar.gz

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 SamNet - Saman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1324
README.md Normal file

File diff suppressed because it is too large Load Diff

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())

6514
paqctl.sh Normal file

File diff suppressed because it is too large Load Diff

42
windows/GFK-Client.bat Normal file
View File

@@ -0,0 +1,42 @@
@echo off
:: GFW-knocker Client Launcher
:: Double-click to run
:: Check for admin rights
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Requesting Administrator privileges...
powershell -Command "Start-Process '%~f0' -Verb RunAs"
exit /b
)
echo.
echo ===============================================
echo GFW-KNOCKER CLIENT (Python/QUIC Proxy)
echo ===============================================
echo.
echo Requirements:
echo - Npcap (will prompt to install)
echo - Python 3.x (will prompt to install)
echo.
echo Once connected, configure your browser:
echo.
echo FIREFOX:
echo Settings ^> Network Settings ^> Settings
echo Select "Manual proxy configuration"
echo SOCKS Host: 127.0.0.1 Port: 1080
echo Select SOCKS v5
echo Check "Proxy DNS when using SOCKS v5"
echo.
echo CHROME (launch with proxy):
echo chrome.exe --proxy-server="socks5://127.0.0.1:1080"
echo.
echo To verify: Visit https://ifconfig.me
echo (Should show your server IP, not your home IP)
echo.
echo Press Ctrl+C to disconnect
echo ===============================================
echo.
:: Run the PowerShell script with gfk backend
powershell -ExecutionPolicy Bypass -NoExit -File "%~dp0paqet-client.ps1" -Backend gfk

38
windows/Paqet-Client.bat Normal file
View File

@@ -0,0 +1,38 @@
@echo off
:: Paqet Client Launcher
:: Double-click to run
:: Check for admin rights
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Requesting Administrator privileges...
powershell -Command "Start-Process '%~f0' -Verb RunAs"
exit /b
)
echo.
echo ===============================================
echo PAQET CLIENT (KCP Raw Socket Proxy)
echo ===============================================
echo.
echo Once connected, configure your browser:
echo.
echo FIREFOX:
echo Settings ^> Network Settings ^> Settings
echo Select "Manual proxy configuration"
echo SOCKS Host: 127.0.0.1 Port: 1080
echo Select SOCKS v5
echo Check "Proxy DNS when using SOCKS v5"
echo.
echo CHROME (launch with proxy):
echo chrome.exe --proxy-server="socks5://127.0.0.1:1080"
echo.
echo To verify: Visit https://ifconfig.me
echo (Should show your server IP, not your home IP)
echo.
echo Press Ctrl+C to disconnect
echo ===============================================
echo.
:: Run the PowerShell script with paqet backend
powershell -ExecutionPolicy Bypass -NoExit -File "%~dp0paqet-client.ps1" -Backend paqet

883
windows/paqet-client.ps1 Normal file
View File

@@ -0,0 +1,883 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Paqet/GFK Windows Client - Bypass Firewall Restrictions
.DESCRIPTION
This script helps you connect to your server through firewalls that block normal connections.
It supports two backends:
PAQET (Recommended for most users)
─────────────────────────────────
• Simple all-in-one solution with built-in SOCKS5 proxy
• Uses KCP protocol over raw sockets to bypass DPI
• Works on: Windows (with Npcap)
• Configuration: Just needs server IP, port, and encryption key
• Proxy: 127.0.0.1:1080 (SOCKS5)
GFW-KNOCKER (For heavily restricted networks)
─────────────────────────────────────────────
• Uses "violated TCP" packets + QUIC tunnel to evade deep packet inspection
• More complex but better at evading sophisticated firewalls (like GFW)
• Works on: Windows (with Npcap + Python)
• Requires: Xray running on server port 443
• Proxy: 127.0.0.1:14000 (forwards to server's Xray SOCKS5)
CAN I RUN BOTH?
───────────────
Yes! Both can run simultaneously on different ports:
• Paqet SOCKS5: 127.0.0.1:1080
• GFK tunnel: 127.0.0.1:14000
This lets you have a backup if one method gets blocked.
.NOTES
Requirements:
• Administrator privileges (for raw socket access)
• Npcap (https://npcap.com) - auto-installed if missing
• Python 3.10+ (GFK only) - auto-installed if missing
#>
param(
[string]$ServerAddr,
[string]$Key,
[string]$Action = "menu", # menu, run, install, config, stop, status
[string]$Backend = "" # paqet, gfk (auto-detect if not specified)
)
$ErrorActionPreference = "Stop"
# Directories and pinned versions (for stability - update after testing new releases)
$InstallDir = "C:\paqet"
$PaqetExe = "$InstallDir\paqet_windows_amd64.exe"
$PaqetVersion = "v1.0.0-alpha.12" # Pinned paqet version
$GfkDir = "$InstallDir\gfk"
$ConfigFile = "$InstallDir\config.yaml"
$SettingsFile = "$InstallDir\settings.conf"
# Npcap (pinned version)
$NpcapVersion = "1.80"
$NpcapUrl = "https://npcap.com/dist/npcap-$NpcapVersion.exe"
$NpcapInstaller = "$env:TEMP\npcap-$NpcapVersion.exe"
# GFK scripts - bundled locally for faster setup
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$GfkLocalDir = "$ScriptDir\..\gfk\client"
$GfkFiles = @("mainclient.py", "quic_client.py", "vio_client.py") # parameters.py is generated
# Colors
function Write-Info { Write-Host "[INFO] $args" -ForegroundColor Cyan }
function Write-Success { Write-Host "[OK] $args" -ForegroundColor Green }
function Write-Warn { Write-Host "[WARN] $args" -ForegroundColor Yellow }
function Write-Err { Write-Host "[ERROR] $args" -ForegroundColor Red }
# Input validation (security: prevent config injection)
function Test-ValidIP {
param([string]$IP)
return $IP -match '^(\d{1,3}\.){3}\d{1,3}$'
}
function Test-ValidMAC {
param([string]$MAC)
return $MAC -match '^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$'
}
function Test-SafeString {
param([string]$s)
# Block characters that could break Python string literals
if ($s.Contains('"') -or $s.Contains("'") -or $s.Contains('\') -or $s.Contains([char]10) -or $s.Contains([char]13)) {
return $false
}
return $true
}
#═══════════════════════════════════════════════════════════════════════
# Prerequisite Checks
#═══════════════════════════════════════════════════════════════════════
function Test-Admin {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]$identity
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Test-Npcap {
$npcapPath = "C:\Windows\System32\Npcap"
$wpcapDll = "C:\Windows\System32\wpcap.dll"
return (Test-Path $npcapPath) -or (Test-Path $wpcapDll)
}
function Test-Python {
try {
$version = & python --version 2>&1
return $version -match "Python 3\."
} catch {
return $false
}
}
function Install-NpcapIfMissing {
if (Test-Npcap) { return $true }
Write-Host ""
Write-Host "===============================================" -ForegroundColor Red
Write-Host " NPCAP REQUIRED" -ForegroundColor Red
Write-Host "===============================================" -ForegroundColor Red
Write-Host ""
Write-Host " Npcap is required for raw socket access."
Write-Host ""
Write-Host " IMPORTANT: During installation, check:" -ForegroundColor Yellow
Write-Host " [x] Install Npcap in WinPcap API-compatible Mode" -ForegroundColor Yellow
Write-Host ""
$choice = Read-Host " Download and install Npcap now? [Y/n]"
if ($choice -match "^[Nn]") {
Write-Warn "Please install Npcap from https://npcap.com"
return $false
}
Write-Info "Downloading Npcap $NpcapVersion..."
try {
Invoke-WebRequest -Uri $NpcapUrl -OutFile $NpcapInstaller -UseBasicParsing
Write-Success "Downloaded"
} catch {
Write-Err "Download failed. Please install manually from https://npcap.com"
Start-Process "https://npcap.com/#download"
return $false
}
Write-Info "Launching Npcap installer..."
Write-Host " Check: [x] WinPcap API-compatible Mode" -ForegroundColor Yellow
Start-Process -FilePath $NpcapInstaller -Wait | Out-Null
Remove-Item $NpcapInstaller -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
if (Test-Npcap) {
Write-Success "Npcap installed!"
return $true
} else {
Write-Err "Npcap installation failed or cancelled"
return $false
}
}
function Install-PythonIfMissing {
if (Test-Python) { return $true }
Write-Host ""
Write-Host "===============================================" -ForegroundColor Red
Write-Host " PYTHON 3 REQUIRED" -ForegroundColor Red
Write-Host "===============================================" -ForegroundColor Red
Write-Host ""
Write-Host " GFW-knocker requires Python 3.x"
Write-Host ""
Write-Host " Please install Python from:" -ForegroundColor Yellow
Write-Host " https://www.python.org/downloads/" -ForegroundColor Yellow
Write-Host ""
Write-Host " IMPORTANT: Check 'Add Python to PATH' during install!" -ForegroundColor Yellow
Write-Host ""
$choice = Read-Host " Open Python download page? [Y/n]"
if ($choice -notmatch "^[Nn]") {
Start-Process "https://www.python.org/downloads/"
}
Read-Host " Press Enter after installing Python"
if (Test-Python) {
Write-Success "Python detected!"
return $true
} else {
Write-Err "Python not found. Please restart PowerShell after installing."
return $false
}
}
function Install-PythonPackages {
Write-Info "Installing Python packages (scapy, aioquic)..."
try {
& python -m pip install --quiet --upgrade pip 2>&1 | Out-Null
& python -m pip install --quiet scapy aioquic 2>&1 | Out-Null
Write-Success "Python packages installed"
return $true
} catch {
Write-Err "Failed to install Python packages: $_"
Write-Info "Try manually: pip install scapy aioquic"
return $false
}
}
#═══════════════════════════════════════════════════════════════════════
# Network Detection
#═══════════════════════════════════════════════════════════════════════
function Get-NetworkInfo {
$adapter = Get-NetAdapter | Where-Object {
$_.Status -eq "Up" -and
$_.InterfaceDescription -notmatch "Virtual|VirtualBox|VMware|Hyper-V|Loopback"
} | Select-Object -First 1
if (-not $adapter) {
$adapter = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -First 1
}
if (-not $adapter) {
Write-Err "No active network adapter found"
return $null
}
$ifIndex = $adapter.ifIndex
$ipConfig = Get-NetIPAddress -InterfaceIndex $ifIndex -AddressFamily IPv4 |
Where-Object { $_.PrefixOrigin -ne "WellKnown" } | Select-Object -First 1
$gateway = Get-NetRoute -InterfaceIndex $ifIndex -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $ipConfig) {
Write-Err "No IPv4 address found on $($adapter.Name)"
return $null
}
$gatewayIP = if ($gateway) { $gateway.NextHop } else { $null }
$gatewayMAC = $null
if ($gatewayIP) {
$null = Test-Connection -ComputerName $gatewayIP -Count 1 -ErrorAction SilentlyContinue
$arpEntry = Get-NetNeighbor -IPAddress $gatewayIP -ErrorAction SilentlyContinue | Select-Object -First 1
if ($arpEntry -and $arpEntry.LinkLayerAddress) {
$gatewayMAC = $arpEntry.LinkLayerAddress -replace "-", ":"
}
}
return @{
Name = $adapter.Name
Guid = $adapter.InterfaceGuid
IP = $ipConfig.IPAddress
GatewayIP = $gatewayIP
GatewayMAC = $gatewayMAC
}
}
#═══════════════════════════════════════════════════════════════════════
# Backend Detection
#═══════════════════════════════════════════════════════════════════════
function Get-InstalledBackend {
if (Test-Path $SettingsFile) {
$content = Get-Content $SettingsFile -ErrorAction SilentlyContinue
foreach ($line in $content) {
if ($line -match '^BACKEND="?(\w+)"?') {
return $Matches[1]
}
}
}
if (Test-Path $PaqetExe) { return "paqet" }
if (Test-Path "$GfkDir\mainclient.py") { return "gfk" }
return $null
}
function Save-Settings {
param([string]$Backend, [string]$ServerAddr = "", [string]$SocksPort = "1080")
$settings = @"
BACKEND="$Backend"
SERVER_ADDR="$ServerAddr"
SOCKS_PORT="$SocksPort"
"@
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
[System.IO.File]::WriteAllText($SettingsFile, $settings)
}
#═══════════════════════════════════════════════════════════════════════
# Paqet Functions
#═══════════════════════════════════════════════════════════════════════
function Install-Paqet {
Write-Host ""
Write-Host " Installing PAQET" -ForegroundColor Green
Write-Host " ────────────────" -ForegroundColor Green
Write-Host " Paqet is an all-in-one proxy solution with built-in SOCKS5."
Write-Host " It uses KCP protocol over raw sockets to bypass firewalls."
Write-Host ""
Write-Host " What will be installed:" -ForegroundColor Yellow
Write-Host " 1. Npcap (for raw socket access)"
Write-Host " 2. Paqet binary"
Write-Host ""
Write-Host " After setup, configure with your server's IP:port and key."
Write-Host " Your proxy will be: 127.0.0.1:1080 (SOCKS5)"
Write-Host ""
if (-not (Install-NpcapIfMissing)) {
Write-Err "Cannot continue without Npcap"
return $false
}
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
if (Test-Path $PaqetExe) {
Write-Info "paqet already installed"
return $true
}
$zipUrl = "https://github.com/SamNet-dev/paqctl/releases/download/$PaqetVersion/paqet-windows-amd64-$PaqetVersion.zip"
$zipFile = "$env:TEMP\paqet.zip"
Write-Info "Downloading paqet $PaqetVersion..."
try {
Invoke-WebRequest -Uri $zipUrl -OutFile $zipFile
} catch {
Write-Err "Download failed: $_"
return $false
}
Write-Info "Extracting..."
Expand-Archive -Path $zipFile -DestinationPath $InstallDir -Force
Remove-Item $zipFile -Force
Write-Success "paqet installed to $InstallDir"
Save-Settings -Backend "paqet"
return $true
}
function New-PaqetConfig {
param(
[Parameter(Mandatory)][string]$Server,
[Parameter(Mandatory)][string]$SecretKey
)
Write-Info "Detecting network..."
$net = Get-NetworkInfo
if (-not $net) { return $false }
Write-Info " Adapter: $($net.Name)"
Write-Info " Local IP: $($net.IP)"
Write-Info " Gateway MAC: $($net.GatewayMAC)"
if (-not $net.GatewayMAC) {
$net.GatewayMAC = Read-Host " Enter gateway MAC (aa:bb:cc:dd:ee:ff)"
}
$guidEscaped = "\\Device\\NPF_$($net.Guid)"
$config = @"
role: "client"
log:
level: "info"
socks5:
- listen: "127.0.0.1:1080"
network:
interface: "$($net.Name)"
guid: "$guidEscaped"
ipv4:
addr: "$($net.IP):0"
router_mac: "$($net.GatewayMAC)"
server:
addr: "$Server"
transport:
protocol: "kcp"
kcp:
mode: "fast"
key: "$SecretKey"
"@
[System.IO.File]::WriteAllText($ConfigFile, $config)
Save-Settings -Backend "paqet" -ServerAddr $Server
Write-Success "Configuration saved"
return $true
}
function Start-Paqet {
if (-not (Test-Npcap)) {
if (-not (Install-NpcapIfMissing)) { return }
}
if (-not (Test-Path $PaqetExe)) {
Write-Err "paqet not installed"
return
}
if (-not (Test-Path $ConfigFile)) {
Write-Err "Config not found. Configure first."
return
}
Write-Host ""
Write-Host " Starting PAQET" -ForegroundColor Green
Write-Host " ──────────────"
Write-Host " Paqet will connect to your server using KCP over raw sockets."
Write-Host ""
Write-Host " Your SOCKS5 proxy will be: 127.0.0.1:1080"
Write-Host " Configure your browser to use this proxy."
Write-Host ""
Write-Info "Starting paqet..."
Write-Info "SOCKS5 proxy: 127.0.0.1:1080"
Write-Info "Press Ctrl+C to stop"
Write-Host ""
& $PaqetExe run -c $ConfigFile
}
#═══════════════════════════════════════════════════════════════════════
# GFW-knocker Functions
#═══════════════════════════════════════════════════════════════════════
function Install-Gfk {
Write-Host ""
Write-Host " Installing GFW-KNOCKER" -ForegroundColor Yellow
Write-Host " ──────────────────────" -ForegroundColor Yellow
Write-Host " GFK is an advanced anti-censorship tool designed for heavy DPI."
Write-Host " It uses 'violated TCP' packets + QUIC tunneling to evade detection."
Write-Host ""
Write-Host " What will be installed:" -ForegroundColor Yellow
Write-Host " 1. Npcap (for raw socket access)"
Write-Host " 2. Python 3.10+ (for QUIC protocol)"
Write-Host " 3. Python packages: scapy, aioquic"
Write-Host " 4. GFK client scripts"
Write-Host ""
Write-Host " IMPORTANT: Your server must have Xray running on port 443." -ForegroundColor Cyan
Write-Host " GFK is just a tunnel - Xray provides the actual SOCKS5 proxy."
Write-Host ""
Write-Host " After setup, your proxy will be: 127.0.0.1:14000 (SOCKS5)"
Write-Host ""
# Check prerequisites
if (-not (Install-NpcapIfMissing)) { return $false }
if (-not (Install-PythonIfMissing)) { return $false }
if (-not (Install-PythonPackages)) { return $false }
# Create directories
if (-not (Test-Path $GfkDir)) {
New-Item -ItemType Directory -Path $GfkDir -Force | Out-Null
}
# Copy bundled GFK scripts (faster than downloading)
Write-Info "Copying GFW-knocker scripts..."
foreach ($file in $GfkFiles) {
$src = "$GfkLocalDir\$file"
$dest = "$GfkDir\$file"
if (Test-Path $src) {
Copy-Item -Path $src -Destination $dest -Force
Write-Info " Copied $file"
} else {
Write-Err "Missing bundled file: $src"
return $false
}
}
Write-Success "GFW-knocker installed to $GfkDir"
Save-Settings -Backend "gfk"
return $true
}
function New-GfkConfig {
param(
[Parameter(Mandatory)][string]$ServerIP,
[Parameter(Mandatory)][string]$AuthCode,
[string]$SocksPort = "1080"
)
# Validate inputs (security: prevent config injection)
if (-not (Test-ValidIP $ServerIP)) {
Write-Err "Invalid server IP format"
return $false
}
if (-not (Test-SafeString $AuthCode)) {
Write-Err "Invalid auth code format"
return $false
}
Write-Info "Detecting network..."
$net = Get-NetworkInfo
if (-not $net) { return $false }
Write-Info " Adapter: $($net.Name)"
Write-Info " Local IP: $($net.IP)"
Write-Info " Gateway: $($net.GatewayMAC)"
if (-not $net.GatewayMAC) {
$net.GatewayMAC = Read-Host " Enter gateway MAC (aa:bb:cc:dd:ee:ff)"
}
# Validate detected network values
if (-not (Test-ValidIP $net.IP)) {
Write-Err "Invalid local IP detected"
return $false
}
if ($net.GatewayMAC -and -not (Test-ValidMAC $net.GatewayMAC)) {
Write-Err "Invalid gateway MAC format"
return $false
}
# Create parameters.py for GFK (matching expected variable names)
$params = @"
# GFW-knocker client configuration (auto-generated)
from scapy.all import conf
# Network interface for scapy (Windows Npcap)
conf.iface = r"\Device\NPF_$($net.Guid)"
my_ip = "$($net.IP)"
gateway_mac = "$($net.GatewayMAC)"
# Server settings
vps_ip = "$ServerIP"
xray_server_ip = "127.0.0.1"
# Port mappings (local_port: remote_port)
tcp_port_mapping = {14000: 443}
udp_port_mapping = {}
# VIO (raw socket) ports
vio_tcp_server_port = 45000
vio_tcp_client_port = 40000
vio_udp_server_port = 35000
vio_udp_client_port = 30000
# QUIC tunnel ports
quic_server_port = 25000
quic_client_port = 20000
quic_local_ip = "127.0.0.1"
# QUIC settings
quic_verify_cert = False
quic_idle_timeout = 86400
udp_timeout = 300
quic_mtu = 1420
quic_max_data = 1073741824
quic_max_stream_data = 1073741824
quic_auth_code = "$AuthCode"
quic_certificate = "cert.pem"
quic_private_key = "key.pem"
# SOCKS proxy
socks_port = $SocksPort
"@
[System.IO.File]::WriteAllText("$GfkDir\parameters.py", $params)
Save-Settings -Backend "gfk" -ServerAddr $ServerIP -SocksPort $SocksPort
Write-Success "GFK configuration saved"
return $true
}
function Start-Gfk {
if (-not (Test-Npcap)) {
if (-not (Install-NpcapIfMissing)) { return }
}
if (-not (Test-Python)) {
Write-Err "Python not found"
return
}
if (-not (Test-Path "$GfkDir\mainclient.py")) {
Write-Err "GFK not installed"
return
}
if (-not (Test-Path "$GfkDir\parameters.py")) {
Write-Err "GFK not configured"
return
}
Write-Host ""
Write-Host " Starting GFW-KNOCKER" -ForegroundColor Yellow
Write-Host " ────────────────────"
Write-Host " This will start:"
Write-Host " 1. VIO client (raw socket handler)"
Write-Host " 2. QUIC client (tunnel to server)"
Write-Host ""
Write-Host " Your SOCKS5 proxy will be: 127.0.0.1:14000"
Write-Host " Configure your browser to use this proxy."
Write-Host ""
Write-Info "Starting GFW-knocker client..."
Write-Info "This will start the raw socket client + Python SOCKS5 proxy"
Write-Info "Press Ctrl+C to stop"
Write-Host ""
# Start GFK client
Push-Location $GfkDir
try {
& python mainclient.py
} finally {
Pop-Location
}
}
function Stop-GfkClient {
# Get-Process doesn't have CommandLine property - use CIM instead
$procs = Get-CimInstance Win32_Process -Filter "Name LIKE 'python%'" -ErrorAction SilentlyContinue |
Where-Object { $_.CommandLine -match "mainclient|gfk" }
if ($procs) {
$procs | ForEach-Object {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
Write-Success "GFK client stopped"
} else {
Write-Info "GFK client not running"
}
}
#═══════════════════════════════════════════════════════════════════════
# Common Functions
#═══════════════════════════════════════════════════════════════════════
function Stop-Client {
# Stop paqet
$paqetProc = Get-Process -Name "paqet_windows_amd64" -ErrorAction SilentlyContinue
if ($paqetProc) {
Stop-Process -Name "paqet_windows_amd64" -Force
Write-Success "paqet stopped"
}
# Stop GFK
Stop-GfkClient
}
function Get-ClientStatus {
Write-Host "`n=== Client Status ===" -ForegroundColor Cyan
$backend = Get-InstalledBackend
Write-Host "Backend: $(if ($backend) { $backend } else { 'Not installed' })"
# Npcap
if (Test-Npcap) {
Write-Success "Npcap: Installed"
} else {
Write-Err "Npcap: NOT installed"
}
# Python (for GFK)
if ($backend -eq "gfk" -or -not $backend) {
if (Test-Python) {
Write-Success "Python: Installed"
} else {
Write-Warn "Python: Not found (needed for GFK)"
}
}
# Paqet
if (Test-Path $PaqetExe) {
Write-Success "Paqet binary: Found"
}
# GFK
if (Test-Path "$GfkDir\mainclient.py") {
Write-Success "GFK scripts: Found"
}
# Config
if (Test-Path $ConfigFile) {
Write-Success "Paqet config: Found"
}
if (Test-Path "$GfkDir\parameters.py") {
Write-Success "GFK config: Found"
}
# Running processes
$paqetRunning = Get-Process -Name "paqet_windows_amd64" -ErrorAction SilentlyContinue
if ($paqetRunning) {
Write-Success "Paqet: RUNNING (PID: $($paqetRunning.Id))"
Write-Info " SOCKS5 proxy: 127.0.0.1:1080"
}
Write-Host ""
}
#═══════════════════════════════════════════════════════════════════════
# Interactive Menu
#═══════════════════════════════════════════════════════════════════════
function Show-Menu {
param([string]$InitBackend = "")
# Use passed backend parameter, or detect if not specified
$backend = if ($InitBackend) { $InitBackend } else { Get-InstalledBackend }
while ($true) {
Write-Host ""
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host " PAQET/GFK CLIENT MANAGER" -ForegroundColor Cyan
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host ""
if ($backend) {
Write-Host " Active backend: " -NoNewline
Write-Host "$backend" -ForegroundColor Green
if ($backend -eq "paqet") {
Write-Host " Proxy: 127.0.0.1:1080 (SOCKS5)" -ForegroundColor DarkGray
} else {
Write-Host " Proxy: 127.0.0.1:14000 (SOCKS5 via tunnel)" -ForegroundColor DarkGray
}
} else {
Write-Host " No backend installed yet" -ForegroundColor Yellow
}
Write-Host ""
Write-Host " 1. Install paqet (simple, all-in-one SOCKS5)"
Write-Host " 2. Install GFW-knocker (advanced, for heavy DPI)"
Write-Host " 3. Configure connection"
Write-Host " 4. Start client"
Write-Host " 5. Stop client"
Write-Host " 6. Show status"
Write-Host " 7. About (how it works)"
Write-Host " 0. Exit"
Write-Host ""
$choice = Read-Host " Select option"
switch ($choice) {
"1" {
if (Install-Paqet) { $backend = "paqet" }
}
"2" {
if (Install-Gfk) { $backend = "gfk" }
}
"3" {
if (-not $backend) {
Write-Warn "Install a backend first (option 1 or 2)"
continue
}
if ($backend -eq "paqet") {
Write-Host ""
Write-Host " PAQET CONFIGURATION" -ForegroundColor Green
Write-Host " Get these values from your server admin or 'paqctl info' on server"
Write-Host ""
$server = Read-Host " Server address (e.g., 1.2.3.4:8443)"
$key = Read-Host " Encryption key (16+ chars)"
if ($server -and $key) {
New-PaqetConfig -Server $server -SecretKey $key
Write-Host ""
Write-Host " Your SOCKS5 proxy: 127.0.0.1:1080" -ForegroundColor Green
}
} else {
Write-Host ""
Write-Host " GFK CONFIGURATION" -ForegroundColor Yellow
Write-Host " Get these values from your server admin or 'paqctl info' on server"
Write-Host ""
$server = Read-Host " Server IP (e.g., 1.2.3.4)"
$auth = Read-Host " Auth code (from server setup)"
if ($server -and $auth) {
New-GfkConfig -ServerIP $server -AuthCode $auth -SocksPort "14000"
Write-Host ""
Write-Host " Your SOCKS5 proxy: 127.0.0.1:14000" -ForegroundColor Green
}
}
}
"4" {
if (-not $backend) {
Write-Warn "Install a backend first"
continue
}
if ($backend -eq "paqet") {
Start-Paqet
} else {
Start-Gfk
}
}
"5" { Stop-Client }
"6" { Get-ClientStatus }
"7" { Show-About }
"0" { return }
default { Write-Warn "Invalid option" }
}
}
}
function Show-About {
Write-Host ""
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host " HOW IT WORKS" -ForegroundColor Cyan
Write-Host "===============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " This tool helps bypass firewall restrictions"
Write-Host " by disguising your traffic. You have TWO options:"
Write-Host ""
Write-Host " --- PAQET - Simple and Fast ---" -ForegroundColor Green
Write-Host " How: Uses KCP protocol over raw sockets"
Write-Host " Proxy: 127.0.0.1:1080 (SOCKS5)"
Write-Host " Best for: Most situations, easy setup"
Write-Host ""
Write-Host " --- GFW-KNOCKER - Advanced Anti-DPI ---" -ForegroundColor Yellow
Write-Host " How: Violated TCP packets + QUIC tunnel"
Write-Host " Proxy: 127.0.0.1:14000 (SOCKS5 via Xray)"
Write-Host " Best for: When paqet is blocked, heavy censorship"
Write-Host ""
Write-Host " --- CAN I RUN BOTH? ---" -ForegroundColor Magenta
Write-Host " YES! They use different ports:"
Write-Host " - Paqet: 127.0.0.1:1080"
Write-Host " - GFK: 127.0.0.1:14000"
Write-Host " Install both as backup - if one gets blocked, use the other!"
Write-Host ""
Write-Host " Press Enter to continue..." -ForegroundColor DarkGray
Read-Host | Out-Null
}
#═══════════════════════════════════════════════════════════════════════
# Main Entry Point
#═══════════════════════════════════════════════════════════════════════
if (-not (Test-Admin)) {
Write-Err "Administrator privileges required"
Write-Info "Right-click PowerShell -> Run as Administrator"
exit 1
}
# Auto-detect backend if not specified
if (-not $Backend) {
$Backend = Get-InstalledBackend
}
switch ($Action.ToLower()) {
"install" {
if ($Backend -eq "gfk") {
Install-Gfk
} else {
Install-Paqet
}
}
"config" {
if ($Backend -eq "gfk") {
if (-not $ServerAddr -or -not $Key) {
Write-Err "Usage: -Action config -ServerAddr [ip] -Key [authcode]"
exit 1
}
New-GfkConfig -ServerIP $ServerAddr -AuthCode $Key
} else {
if (-not $ServerAddr -or -not $Key) {
Write-Err "Usage: -Action config -ServerAddr [ip:port] -Key [key]"
exit 1
}
New-PaqetConfig -Server $ServerAddr -SecretKey $Key
}
}
"run" {
if ($ServerAddr -and $Key) {
if ($Backend -eq "gfk") {
Install-Gfk
New-GfkConfig -ServerIP $ServerAddr -AuthCode $Key
Start-Gfk
} else {
Install-Paqet
New-PaqetConfig -Server $ServerAddr -SecretKey $Key
Start-Paqet
}
} else {
if ($Backend -eq "gfk") {
Start-Gfk
} else {
Start-Paqet
}
}
}
"start" {
if ($Backend -eq "gfk") { Start-Gfk } else { Start-Paqet }
}
"stop" { Stop-Client }
"status" { Get-ClientStatus }
"menu" { Show-Menu -InitBackend $Backend }
default { Show-Menu -InitBackend $Backend }
}