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