Initial commit of PyFrp project
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Galaxy Vastar Software Studio
|
||||
|
||||
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.
|
||||
2
__init__.py
Normal file
2
__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .client import PortForwardClient
|
||||
from .server import PortForwardServer
|
||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/client.cpython-312.pyc
Normal file
BIN
__pycache__/client.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/server.cpython-312.pyc
Normal file
BIN
__pycache__/server.cpython-312.pyc
Normal file
Binary file not shown.
7
backup/__init__.py
Normal file
7
backup/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Brief Introduction
|
||||
# The original Frp (https://github.com/Fatedier/frp/) is somewhat bloated in size,
|
||||
# while PyFrp is a simplified Python-based implementation with a smaller footprint and fewer features.
|
||||
|
||||
from . import client
|
||||
from . import server
|
||||
208
backup/client.py
Normal file
208
backup/client.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Brief Introduction
|
||||
# The original Frp (https://github.com/Fatedier/frp/) is somewhat bloated in size,
|
||||
# while PyFrp is a simplified Python-based implementation with a smaller footprint and fewer features.
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import traceback
|
||||
import time
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
import base64
|
||||
import sys
|
||||
import os
|
||||
|
||||
class AesEncryptor:
|
||||
def __init__(self, Key):
|
||||
self.Key = Key.ljust(32)[:32].encode('utf-8')
|
||||
self.Mode = AES.MODE_CBC
|
||||
def Encrypt(self, Data):
|
||||
Iv = b'0123456789abcdef'
|
||||
Cipher = AES.new(self.Key, self.Mode, Iv)
|
||||
return base64.b64encode(Iv + Cipher.encrypt(pad(Data.encode('utf-8'), AES.block_size))).decode('utf-8')
|
||||
def Decrypt(self, Data):
|
||||
Data = base64.b64decode(Data)
|
||||
Iv = Data[:16]
|
||||
Cipher = AES.new(self.Key, self.Mode, Iv)
|
||||
return unpad(Cipher.decrypt(Data[16:]), AES.block_size).decode('utf-8')
|
||||
|
||||
class PortMapping:
|
||||
def __init__(self, ForwardHost, ForwardPort, TargetPort, Mode):
|
||||
self.ForwardHost = ForwardHost
|
||||
self.ForwardPort = ForwardPort
|
||||
self.TargetPort = TargetPort
|
||||
self.Mode = Mode.lower()
|
||||
self.IsActive = False
|
||||
def ToDict(self):
|
||||
return {
|
||||
'forward_host': self.ForwardHost,
|
||||
'forward_port': self.ForwardPort,
|
||||
'target_port': self.TargetPort,
|
||||
'mode': self.Mode
|
||||
}
|
||||
|
||||
class PortForwardClient:
|
||||
def __init__(self, ServerHost="127.0.0.1", ServerPort=5000, Key="07A36AEF1907843"):
|
||||
self.ServerHost = ServerHost
|
||||
self.ServerPort = ServerPort
|
||||
self.Key = Key
|
||||
self.Socket = None
|
||||
self.IsRunning = False
|
||||
self.Mappings = []
|
||||
self.MappingsLock = threading.Lock()
|
||||
self.Encryptor = AesEncryptor(Key)
|
||||
def Log(self, Message):
|
||||
Timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f"[{Timestamp}] [Client] {Message}")
|
||||
def HandleError(self, Message):
|
||||
self.Log(f"Error: {Message}")
|
||||
self.Log(traceback.format_exc())
|
||||
def Connect(self):
|
||||
try:
|
||||
self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.Socket.connect((self.ServerHost, self.ServerPort))
|
||||
self.Log(f"Connected to server {self.ServerHost}:{self.ServerPort}")
|
||||
return True
|
||||
except Exception as E:
|
||||
self.HandleError(f"Connection failed: {str(E)}")
|
||||
self.Socket = None
|
||||
return False
|
||||
def AddMapping(self, ForwardHost, ForwardPort, TargetPort, Mode):
|
||||
with self.MappingsLock:
|
||||
for M in self.Mappings:
|
||||
if M.TargetPort == TargetPort:
|
||||
self.Log(f"Mapping for target port {TargetPort} already exists")
|
||||
return False
|
||||
Mapping = PortMapping(ForwardHost, ForwardPort, TargetPort, Mode)
|
||||
self.Mappings.append(Mapping)
|
||||
# 仅当已连接时才立即注册映射
|
||||
if self.Socket is not None:
|
||||
return self.RegisterMapping(Mapping)
|
||||
return True # 连接后会自动注册
|
||||
def RegisterMapping(self, Mapping):
|
||||
if self.Socket is None:
|
||||
self.Log("Cannot register mapping - not connected to server")
|
||||
return False
|
||||
try:
|
||||
Command = {
|
||||
'type': 'register',
|
||||
'timestamp': str(time.time()),
|
||||
**Mapping.ToDict()
|
||||
}
|
||||
EncryptedData = self.Encryptor.Encrypt(json.dumps(Command))
|
||||
self.Socket.sendall((EncryptedData + '\n').encode('utf-8'))
|
||||
ResponseData = self.Socket.recv(4096).decode('utf-8').strip()
|
||||
Response = json.loads(self.Encryptor.Decrypt(ResponseData))
|
||||
if Response.get('status') == 'success':
|
||||
self.Log(f"Successfully registered mapping: {Mapping.TargetPort} -> {Mapping.ForwardHost}:{Mapping.ForwardPort} ({Mapping.Mode})")
|
||||
Mapping.IsActive = True
|
||||
return True
|
||||
else:
|
||||
self.Log(f"Failed to register mapping: {Response.get('message', 'Unknown error')}")
|
||||
return False
|
||||
except Exception as E:
|
||||
self.HandleError(f"Error registering mapping: {str(E)}")
|
||||
return False
|
||||
def RemoveMapping(self, TargetPort):
|
||||
with self.MappingsLock:
|
||||
for I, M in enumerate(self.Mappings):
|
||||
if M.TargetPort == TargetPort:
|
||||
if self.UnregisterMapping(M):
|
||||
del self.Mappings[I]
|
||||
return True
|
||||
return False
|
||||
self.Log(f"No mapping found for target port {TargetPort}")
|
||||
return False
|
||||
def UnregisterMapping(self, Mapping):
|
||||
try:
|
||||
Command = {
|
||||
'type': 'unregister',
|
||||
'timestamp': str(time.time()),
|
||||
'target_port': Mapping.TargetPort
|
||||
}
|
||||
EncryptedData = self.Encryptor.Encrypt(json.dumps(Command))
|
||||
self.Socket.sendall((EncryptedData + '\n').encode('utf-8'))
|
||||
ResponseData = self.Socket.recv(4096).decode('utf-8').strip()
|
||||
Response = json.loads(self.Encryptor.Decrypt(ResponseData))
|
||||
if Response.get('status') == 'success':
|
||||
self.Log(f"Successfully unregistered mapping: {Mapping.TargetPort}")
|
||||
Mapping.IsActive = False
|
||||
return True
|
||||
else:
|
||||
self.Log(f"Failed to unregister mapping: {Response.get('message', 'Unknown error')}")
|
||||
return False
|
||||
except Exception as E:
|
||||
self.HandleError(f"Error unregistering mapping: {str(E)}")
|
||||
return False
|
||||
def Start(self):
|
||||
if not self.Connect():
|
||||
return
|
||||
self.IsRunning = True
|
||||
self.Log("Client started")
|
||||
try:
|
||||
# 连接成功后注册所有映射
|
||||
with self.MappingsLock:
|
||||
for Mapping in self.Mappings:
|
||||
self.RegisterMapping(Mapping)
|
||||
while self.IsRunning:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
self.Log("Client interrupted")
|
||||
except Exception as E:
|
||||
self.HandleError(f"Client error: {str(E)}")
|
||||
finally:
|
||||
self.Stop()
|
||||
def Stop(self):
|
||||
self.IsRunning = False
|
||||
with self.MappingsLock:
|
||||
for Mapping in self.Mappings:
|
||||
if Mapping.IsActive:
|
||||
self.UnregisterMapping(Mapping)
|
||||
self.Mappings.clear()
|
||||
if self.Socket:
|
||||
try:
|
||||
self.Socket.close()
|
||||
except:
|
||||
pass
|
||||
self.Log("Client stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Config = {
|
||||
"server_host": "127.0.0.1",
|
||||
"server_port": 5000,
|
||||
"key": "07A36AEF1907843",
|
||||
"mappings": [
|
||||
{
|
||||
"forward_host": "127.0.0.1",
|
||||
"forward_port": 5902,
|
||||
"target_port": 5500,
|
||||
"mode": "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
with open(sys.argv[1], 'r') as F:
|
||||
Config.update(json.load(F))
|
||||
except Exception as E:
|
||||
print(f"Error loading config file: {str(E)}")
|
||||
os._exit(0)
|
||||
Client = PortForwardClient(
|
||||
ServerHost=Config["server_host"],
|
||||
ServerPort=int(Config["server_port"]),
|
||||
Key=Config["key"]
|
||||
)
|
||||
for Mapping in Config["mappings"]:
|
||||
Client.AddMapping(
|
||||
ForwardHost=Mapping["forward_host"],
|
||||
ForwardPort=int(Mapping["forward_port"]),
|
||||
TargetPort=int(Mapping["target_port"]),
|
||||
Mode=Mapping["mode"]
|
||||
)
|
||||
try:
|
||||
Client.Start()
|
||||
except KeyboardInterrupt:
|
||||
Client.Stop()
|
||||
181
backup/readme.md
Normal file
181
backup/readme.md
Normal file
@@ -0,0 +1,181 @@
|
||||
<p align="center">
|
||||
<img src="https://avatars.githubusercontent.com/u/193612261?v=4" width="270px" />
|
||||
<p align="center">PyFrp</p>
|
||||
<p align="center">Frp (Fast Reverse Proxy) implemented in Python</p>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
> **This project is only in its early stages, and the documentation is currently incomplete. Thank you for using this project. If possible, please give it a star ♥**
|
||||
|
||||
# PyFrp
|
||||
|
||||
## 🗡 Brief Introduction
|
||||
The original Frp (https://github.com/Fatedier/frp/) is somewhat bloated in size, while PyFrp is a simplified Python-based implementation with a smaller footprint and fewer features.
|
||||
|
||||
### Comparison with the Original Frp
|
||||
| Feature| PyFrp | Frp|
|
||||
|:---------------:|:-----:|:----:|
|
||||
| Quick Setup| ✅| ✅|
|
||||
| Size| ✅| ❌|
|
||||
| SSL Support| ❌| ✅|
|
||||
| Embeddable| ✅| ✅❌ |
|
||||
| Documentation| ❌| ✅|
|
||||
|
||||
Choose based on your needs:
|
||||
- If you prioritize **quick setup, small size, simplicity, and embeddability** (e.g., integrating into your Python project), PyFrp is a great choice.
|
||||
- For **production deployments**, Frp remains the better option for now.
|
||||
|
||||
In the future, we aim to enhance PyFrp's functionality, add more configuration options, and potentially support additional protocols (e.g., HTTP/HTTPS).
|
||||
|
||||
We’re just getting started and would greatly appreciate your support—**a ⭐ Star** would mean a lot to us!
|
||||
|
||||
If needed, we could also develop a **C-based implementation** of Frp.
|
||||
|
||||
> Note: **HTTPS is not yet supported**. We may add this later, and contributions are welcome!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
The only third-party library you need is `pycryptodome`. Install it with:
|
||||
```bash
|
||||
pip install pycryptodome
|
||||
```
|
||||
We use its encryption algorithms to secure your data. While any version of `pycryptodome` should work, we recommend **v3.22.0** if you encounter issues:
|
||||
```bash
|
||||
pip install pycryptodome==3.22.0
|
||||
```
|
||||
For a **non-encrypted version**, simply remove all encryption-related code.
|
||||
|
||||
### Configuration
|
||||
#### Server (`server_config.json`):
|
||||
```python
|
||||
{
|
||||
"internal_data_port": 5000, // PyFrp server data port
|
||||
"allowed_port_range": "5001-5500", // Allowed port range
|
||||
"max_ports_per_client": 5, // Max ports per client
|
||||
"key": "07A36AEF1907843" // Encryption key
|
||||
}
|
||||
```
|
||||
|
||||
#### Client (`client_config.json`):
|
||||
```python
|
||||
{
|
||||
"server_host": "127.0.0.1", // PyFrp server address
|
||||
"server_port": 5000, // PyFrp server port
|
||||
"key": "07A36AEF1907843", // Encryption key
|
||||
"mappings": [ // Port mappings
|
||||
{
|
||||
"forward_host": "127.0.0.1", // Local host
|
||||
"forward_port": 5902, // Local port
|
||||
"target_port": 5500, // Target port
|
||||
"mode": "tcp" // Protocol (TCP/UDP)
|
||||
}
|
||||
// Add more mappings as needed
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Run with custom configs:
|
||||
```bash
|
||||
python server.py server_config.json
|
||||
python client.py client_config.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact Us
|
||||
- 📧 Email: wyt18222152539wyt@163.com
|
||||
- 🌐 Website: [Galaxy Vastar Software Studio](https://www.gvsds.com)
|
||||
- 📱 WeChat: GVSADS
|
||||
|
||||
---
|
||||
|
||||
## 🗡 简单介绍
|
||||
原本的 Frp https://github.com/Fatedier/frp/ 体积略显臃肿,而 PyFrp 则是一个基于 Python 实现的简单版本,体积更小,功能也更简单。
|
||||
|
||||
我们 和 原版 Frp 相比
|
||||
| 功能 | PyFrp | Frp |
|
||||
|:-:|:-:|:-:|
|
||||
| 快速配置 | ✅ | ✅ |
|
||||
| 体积 | ✅ | ❌ |
|
||||
| SSL 功能 | ❌ | ✅ |
|
||||
| 嵌入式 | ✅ | ✅❌ |
|
||||
| 文档 | ❌ | ✅ |
|
||||
|
||||
请您根据您的需要选择使用哪个,
|
||||
- 如果您需要快速配置,体积小,功能简单,嵌入式,集成到您自己的 Python 项目中,那么 PyFrp 是一个不错的选择。
|
||||
- 如果您需要部署在生产环境中,暂时不要选择 PyFrp,Frp 是一个更好的选择。
|
||||
|
||||
日后,我们以 Frp 为目标,完善其功能,添加更多的配置项,同时也会考虑添加更多的协议支持,如 http、https 等。
|
||||
|
||||
我们其实也才起步,仍然需要您的支持,如果可以,还请您点一个 Star ⭐,这将是对我们最大的支持。
|
||||
|
||||
如果您有需要,我们可以设计一版 C 实现的 Frp。
|
||||
|
||||
> 注意,我们暂时还不支持 https 协议。稍后如果有时间,我们可能会考虑支持,如果您已经帮我们支持,随时欢迎您提交。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
你唯一需要下载的第三方库是 `pycryptodome`,使用以下命令安装:
|
||||
```bash
|
||||
pip install pycryptodome
|
||||
```
|
||||
我们使用它的加密算法来保护你的数据。我们可知的是任意版本的 `pycryptodome` 都可以使用。但如果出现加密算法的报错,我们建议你使用最新版本的 `pycryptodome`,或者使用我们所使用的 3.22.0 版本,即:
|
||||
```bash
|
||||
pip install pycryptodome==3.22.0
|
||||
```
|
||||
如果你期望使用一个不带加密的版本,你可以将所有涉及加密的部分全部删掉。
|
||||
|
||||
|
||||
|
||||
然后,你可以修改源代码中的个人配置
|
||||
|
||||
server端配置修改:
|
||||
```python
|
||||
{
|
||||
"internal_data_port": 5000, // PyFrp 服务器端数据端口
|
||||
"allowed_port_range": "5001-5500", // 允许的端口范围
|
||||
"max_ports_per_client": 5, // 每个客户端最大端口数
|
||||
"key": "07A36AEF1907843" // 加密密钥
|
||||
}
|
||||
```
|
||||
|
||||
client
|
||||
```python
|
||||
{
|
||||
"server_host": "127.0.0.1", // PyFrp 服务器端主机地址
|
||||
"server_port": 5000, // PyFrp 服务器端端口
|
||||
"key": "07A36AEF1907843", // 加密密钥
|
||||
"mappings": [ // 端口映射配置
|
||||
{
|
||||
"forward_host": "127.0.0.1", // 本地主机地址
|
||||
"forward_port": 5902, // 本地端口
|
||||
"target_port": 5500, // 目标端口
|
||||
"mode": "tcp" // 传输模式
|
||||
},
|
||||
// 你可以在这里输入更多的端口映射配置
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
允许你直接修改代码中尾部的 Config 变量来实现自定义配置。或者指定配置文件位置。
|
||||
```bash
|
||||
python server.py server_config.json
|
||||
python client.py client_config.json
|
||||
```
|
||||
|
||||
## 📞 联系我们
|
||||
- 📧 Email: wyt18222152539wyt@163.com
|
||||
- 🌐 官网: [银河万通软件开发工作室](https://www.gvsds.com)
|
||||
- 📱 微信: GVSADS
|
||||
324
backup/server.py
Normal file
324
backup/server.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Brief Introduction
|
||||
# The original Frp (https://github.com/Fatedier/frp/) is somewhat bloated in size,
|
||||
# while PyFrp is a simplified Python-based implementation with a smaller footprint and fewer features.
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import traceback
|
||||
import time
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
import base64
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
class AesEncryptor:
|
||||
def __init__(self, Key):
|
||||
self.Key = Key.ljust(32)[:32].encode('utf-8')
|
||||
self.Mode = AES.MODE_CBC
|
||||
def Encrypt(self, Data):
|
||||
Iv = b'0123456789abcdef'
|
||||
Cipher = AES.new(self.Key, self.Mode, Iv)
|
||||
return base64.b64encode(Iv + Cipher.encrypt(pad(Data.encode('utf-8'), AES.block_size))).decode('utf-8')
|
||||
def Decrypt(self, Data):
|
||||
Data = base64.b64decode(Data)
|
||||
Iv = Data[:16]
|
||||
Cipher = AES.new(self.Key, self.Mode, Iv)
|
||||
return unpad(Cipher.decrypt(Data[16:]), AES.block_size).decode('utf-8')
|
||||
|
||||
class PortRange:
|
||||
def __init__(self, RangeStr):
|
||||
Match = re.match(r'^(\d+)-(\d+)$', RangeStr)
|
||||
if not Match:
|
||||
raise ValueError("Invalid port range format")
|
||||
self.Start = int(Match.group(1))
|
||||
self.End = int(Match.group(2))
|
||||
if self.Start < 1 or self.End > 65535 or self.Start > self.End:
|
||||
raise ValueError("Invalid port range values")
|
||||
def IsInRange(self, Port):
|
||||
return self.Start <= Port <= self.End
|
||||
|
||||
class ClientHandler:
|
||||
def __init__(self, Server, ClientSocket, Address):
|
||||
self.Server = Server
|
||||
self.ClientSocket = ClientSocket
|
||||
self.Address = Address
|
||||
self.IsRunning = True
|
||||
self.Mappings = {}
|
||||
self.Encryptor = AesEncryptor(Server.Key)
|
||||
self.Log("New client connected")
|
||||
def Log(self, Message):
|
||||
Timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f"[{Timestamp}] [Client {self.Address}] {Message}")
|
||||
def HandleError(self, Message):
|
||||
self.Log(f"Error: {Message}")
|
||||
self.Log(traceback.format_exc())
|
||||
def Close(self):
|
||||
self.IsRunning = False
|
||||
for Mapping in self.Mappings.values():
|
||||
Mapping.Close()
|
||||
self.Mappings.clear()
|
||||
try:
|
||||
self.ClientSocket.close()
|
||||
except:
|
||||
pass
|
||||
self.Log("Client disconnected")
|
||||
def CreateTcpMapping(self, ForwardHost, ForwardPort, TargetPort):
|
||||
try:
|
||||
ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
ServerSocket.bind(('0.0.0.0', TargetPort))
|
||||
ServerSocket.listen(5)
|
||||
Mapping = TcpMapping(self, ServerSocket, ForwardHost, ForwardPort, TargetPort)
|
||||
self.Mappings[TargetPort] = Mapping
|
||||
threading.Thread(target=Mapping.Start, daemon=True).start()
|
||||
return True
|
||||
except Exception as E:
|
||||
self.HandleError(f"Failed to create TCP mapping: {str(E)}")
|
||||
return False
|
||||
def RemoveMapping(self, TargetPort):
|
||||
if TargetPort in self.Mappings:
|
||||
self.Mappings[TargetPort].Close()
|
||||
del self.Mappings[TargetPort]
|
||||
return True
|
||||
return False
|
||||
def ProcessCommand(self, Command):
|
||||
try:
|
||||
CmdType = Command.get('type')
|
||||
if CmdType == 'register':
|
||||
if len(self.Mappings) >= self.Server.MaxPortsPerClient:
|
||||
return {'status': 'error', 'message': f'Max {self.Server.MaxPortsPerClient} ports per client'}
|
||||
TargetPort = Command.get('target_port')
|
||||
if not self.Server.AllowedPorts.IsInRange(TargetPort):
|
||||
return {'status': 'error', 'message': f'Target port {TargetPort} not in allowed range'}
|
||||
if TargetPort in self.Server.GetAllUsedPorts():
|
||||
return {'status': 'error', 'message': f'Target port {TargetPort} already in use'}
|
||||
Mode = Command.get('mode', 'tcp').lower()
|
||||
ForwardHost = Command.get('forward_host', '127.0.0.1')
|
||||
ForwardPort = Command.get('forward_port')
|
||||
if not ForwardPort:
|
||||
return {'status': 'error', 'message': 'Forward port is required'}
|
||||
Success = False
|
||||
if Mode == 'tcp':
|
||||
Success = self.CreateTcpMapping(ForwardHost, ForwardPort, TargetPort)
|
||||
else:
|
||||
return {'status': 'error', 'message': f'Unsupported mode {Mode}'}
|
||||
if Success:
|
||||
return {'status': 'success', 'message': f'Mapping created: {TargetPort} -> {ForwardHost}:{ForwardPort} ({Mode})'}
|
||||
else:
|
||||
return {'status': 'error', 'message': 'Failed to create mapping'}
|
||||
elif CmdType == 'unregister':
|
||||
TargetPort = Command.get('target_port')
|
||||
if self.RemoveMapping(TargetPort):
|
||||
return {'status': 'success', 'message': f'Mapping removed: {TargetPort}'}
|
||||
else:
|
||||
return {'status': 'error', 'message': f'No mapping found for {TargetPort}'}
|
||||
else:
|
||||
return {'status': 'error', 'message': f'Unknown command type {CmdType}'}
|
||||
except Exception as E:
|
||||
self.HandleError(f"Error processing command: {str(E)}")
|
||||
return {'status': 'error', 'message': str(E)}
|
||||
def Run(self):
|
||||
try:
|
||||
self.ClientSocket.settimeout(30)
|
||||
while self.IsRunning:
|
||||
Data = b''
|
||||
while True:
|
||||
try:
|
||||
Chunk = self.ClientSocket.recv(1024)
|
||||
if not Chunk:
|
||||
self.Log("Connection closed by client")
|
||||
self.Close()
|
||||
return
|
||||
Data += Chunk
|
||||
if b'\n' in Data:
|
||||
break
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as E:
|
||||
self.HandleError(f"Receive error: {str(E)}")
|
||||
self.Close()
|
||||
return
|
||||
try:
|
||||
EncryptedData = Data.decode('utf-8').strip()
|
||||
DecryptedData = self.Encryptor.Decrypt(EncryptedData)
|
||||
Command = json.loads(DecryptedData)
|
||||
Timestamp = Command.get('timestamp')
|
||||
if not Timestamp or abs(time.time() - float(Timestamp)) > 30:
|
||||
Response = {'status': 'error', 'message': 'Invalid or expired timestamp'}
|
||||
else:
|
||||
Response = self.ProcessCommand(Command)
|
||||
Response['timestamp'] = str(time.time())
|
||||
EncryptedResponse = self.Encryptor.Encrypt(json.dumps(Response))
|
||||
self.ClientSocket.sendall((EncryptedResponse + '\n').encode('utf-8'))
|
||||
except Exception as E:
|
||||
self.HandleError(f"Data processing error: {str(E)}")
|
||||
Response = {'status': 'error', 'message': 'Invalid data format'}
|
||||
EncryptedResponse = self.Encryptor.Encrypt(json.dumps(Response))
|
||||
self.ClientSocket.sendall((EncryptedResponse + '\n').encode('utf-8'))
|
||||
except Exception as E:
|
||||
self.HandleError(f"Client handler error: {str(E)}")
|
||||
finally:
|
||||
self.Close()
|
||||
|
||||
class TcpMapping:
|
||||
def __init__(self, ClientHandler, ServerSocket, ForwardHost, ForwardPort, TargetPort):
|
||||
self.ClientHandler = ClientHandler
|
||||
self.ServerSocket = ServerSocket
|
||||
self.ForwardHost = ForwardHost
|
||||
self.ForwardPort = ForwardPort
|
||||
self.TargetPort = TargetPort
|
||||
self.IsRunning = True
|
||||
self.ClientHandler.Log(f"TCP mapping created: {TargetPort} -> {ForwardHost}:{ForwardPort}")
|
||||
def Log(self, Message):
|
||||
self.ClientHandler.Log(f"[TCP {self.TargetPort}] {Message}")
|
||||
def HandleError(self, Message):
|
||||
self.Log(f"Error: {Message}")
|
||||
self.Log(traceback.format_exc())
|
||||
def Close(self):
|
||||
self.IsRunning = False
|
||||
try:
|
||||
self.ServerSocket.close()
|
||||
except:
|
||||
pass
|
||||
self.Log("Mapping closed")
|
||||
def Relay(self, SrcSocket, DstSocket, Direction):
|
||||
try:
|
||||
while self.IsRunning:
|
||||
Data = SrcSocket.recv(4096)
|
||||
if not Data:
|
||||
break
|
||||
DstSocket.sendall(Data)
|
||||
self.Log(f"Relayed {len(Data)} bytes {Direction}")
|
||||
except Exception as E:
|
||||
self.HandleError(f"Relay error {Direction}: {str(E)}")
|
||||
finally:
|
||||
try:
|
||||
SrcSocket.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
DstSocket.close()
|
||||
except:
|
||||
pass
|
||||
def HandleConnection(self, ClientSocket, Address):
|
||||
self.Log(f"New connection from {Address}")
|
||||
try:
|
||||
ForwardSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
ForwardSocket.connect((self.ForwardHost, self.ForwardPort))
|
||||
self.Log(f"Connected to forward target {self.ForwardHost}:{self.ForwardPort}")
|
||||
threading.Thread(target=self.Relay, args=(ClientSocket, ForwardSocket, f"to {self.ForwardHost}:{self.ForwardPort}"), daemon=True).start()
|
||||
threading.Thread(target=self.Relay, args=(ForwardSocket, ClientSocket, f"from {self.ForwardHost}:{self.ForwardPort}"), daemon=True).start()
|
||||
except Exception as E:
|
||||
self.HandleError(f"Failed to connect to forward target: {str(E)}")
|
||||
try:
|
||||
ClientSocket.close()
|
||||
except:
|
||||
pass
|
||||
def Start(self):
|
||||
try:
|
||||
while self.IsRunning:
|
||||
self.ServerSocket.settimeout(1)
|
||||
try:
|
||||
ClientSocket, Address = self.ServerSocket.accept()
|
||||
ClientSocket.settimeout(None)
|
||||
threading.Thread(target=self.HandleConnection, args=(ClientSocket, Address), daemon=True).start()
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as E:
|
||||
if self.IsRunning:
|
||||
self.HandleError(f"Accept error: {str(E)}")
|
||||
break
|
||||
except Exception as E:
|
||||
self.HandleError(f"Mapping error: {str(E)}")
|
||||
finally:
|
||||
self.Close()
|
||||
|
||||
class PortForwardServer:
|
||||
def __init__(self, InternalPort=5000, AllowedPortsRange="5001-5500", MaxPortsPerClient=5, Key="07A36AEF1907843"):
|
||||
self.InternalPort = InternalPort
|
||||
self.AllowedPorts = PortRange(AllowedPortsRange)
|
||||
self.MaxPortsPerClient = MaxPortsPerClient
|
||||
self.Key = Key
|
||||
self.ServerSocket = None
|
||||
self.IsRunning = False
|
||||
self.Clients = []
|
||||
self.ClientsLock = threading.Lock()
|
||||
def Log(self, Message):
|
||||
Timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f"[{Timestamp}] [Server] {Message}")
|
||||
def HandleError(self, Message):
|
||||
self.Log(f"Error: {Message}")
|
||||
self.Log(traceback.format_exc())
|
||||
def GetAllUsedPorts(self):
|
||||
UsedPorts = set()
|
||||
with self.ClientsLock:
|
||||
for Client in self.Clients:
|
||||
UsedPorts.update(Client.Mappings.keys())
|
||||
return UsedPorts
|
||||
def RemoveClient(self, Client):
|
||||
with self.ClientsLock:
|
||||
if Client in self.Clients:
|
||||
self.Clients.remove(Client)
|
||||
self.Log(f"Client removed. Total clients: {len(self.Clients)}")
|
||||
def Start(self):
|
||||
try:
|
||||
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.ServerSocket.bind(('0.0.0.0', self.InternalPort))
|
||||
self.ServerSocket.listen(5)
|
||||
self.IsRunning = True
|
||||
self.Log(f"Server started on port {self.InternalPort}")
|
||||
self.Log(f"Allowed ports: {self.AllowedPorts.Start}-{self.AllowedPorts.End}")
|
||||
self.Log(f"Max ports per client: {self.MaxPortsPerClient}")
|
||||
while self.IsRunning:
|
||||
ClientSocket, Address = self.ServerSocket.accept()
|
||||
self.Log(f"New connection from {Address}")
|
||||
Client = ClientHandler(self, ClientSocket, Address)
|
||||
with self.ClientsLock:
|
||||
self.Clients.append(Client)
|
||||
self.Log(f"Client added. Total clients: {len(self.Clients)}")
|
||||
threading.Thread(target=lambda: [Client.Run(), self.RemoveClient(Client)], daemon=True).start()
|
||||
except Exception as E:
|
||||
self.HandleError(f"Server error: {str(E)}")
|
||||
self.Stop()
|
||||
def Stop(self):
|
||||
self.IsRunning = True
|
||||
if self.ServerSocket:
|
||||
try:
|
||||
self.ServerSocket.close()
|
||||
except:
|
||||
pass
|
||||
with self.ClientsLock:
|
||||
for Client in self.Clients:
|
||||
Client.Close()
|
||||
self.Clients.clear()
|
||||
self.Log("Server stopped")
|
||||
|
||||
if __name__ == "__main__":
|
||||
Config = {
|
||||
"internal_data_port": 5000,
|
||||
"allowed_port_range": "5001-5500",
|
||||
"max_ports_per_client": 5,
|
||||
"key": "07A36AEF1907843"
|
||||
}
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
with open(sys.argv[1], 'r') as F:
|
||||
Config.update(json.load(F))
|
||||
except Exception as E:
|
||||
print(f"Error loading config file: {str(E)}")
|
||||
os._exit(0)
|
||||
Server = PortForwardServer(
|
||||
InternalPort=int(Config["internal_data_port"]),
|
||||
AllowedPortsRange=Config["allowed_port_range"],
|
||||
MaxPortsPerClient=int(Config["max_ports_per_client"]),
|
||||
Key=Config["key"]
|
||||
)
|
||||
try:
|
||||
Server.Start()
|
||||
except KeyboardInterrupt:
|
||||
Server.Stop()
|
||||
295
client.py
Normal file
295
client.py
Normal file
@@ -0,0 +1,295 @@
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import traceback
|
||||
import sys
|
||||
import time
|
||||
|
||||
class PortForwardClient:
|
||||
def __init__(self, ServerDomain="127.0.0.1", ServerPort=5000, Forwards=None, Key="07A36AEF1907843"):
|
||||
self.ServerDomain = ServerDomain
|
||||
self.ServerPort = ServerPort
|
||||
self.Forwards = Forwards or []
|
||||
self.Key = Key
|
||||
self.ServerSocket = None
|
||||
self.Running = True
|
||||
self.ForwardMap = {}
|
||||
self.ConnectionMap = {}
|
||||
self.Lock = threading.Lock()
|
||||
self.Buffer = b''
|
||||
self.MessageSeparator = b'|||'
|
||||
|
||||
def Start(self):
|
||||
try:
|
||||
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.ServerSocket.connect((self.ServerDomain, self.ServerPort))
|
||||
print(f"Connected to server {self.ServerDomain}:{self.ServerPort}")
|
||||
self.Authenticate()
|
||||
threading.Thread(target=self.ReceiveFromServer, daemon=True).start()
|
||||
self.SetupForwards()
|
||||
while self.Running:
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"Client start error: {e}")
|
||||
traceback.print_exc()
|
||||
self.Stop()
|
||||
|
||||
def Stop(self):
|
||||
self.Running = False
|
||||
with self.Lock:
|
||||
for forwardId, forwardData in self.ForwardMap.items():
|
||||
try:
|
||||
self.SendToServer({'type': 'close_forward', 'forward_id': forwardId})
|
||||
except:
|
||||
pass
|
||||
for connId, conn in forwardData['connections'].items():
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
self.ForwardMap.clear()
|
||||
self.ConnectionMap.clear()
|
||||
if self.ServerSocket:
|
||||
try:
|
||||
self.ServerSocket.close()
|
||||
except:
|
||||
pass
|
||||
print("Client stopped")
|
||||
|
||||
def Authenticate(self):
|
||||
self.SendToServer({'type': 'auth', 'key': self.Key})
|
||||
response = self.ServerSocket.recv(4096)
|
||||
if not response:
|
||||
raise ConnectionError("Server closed connection during authentication")
|
||||
self.Buffer += response
|
||||
self.ProcessBuffer()
|
||||
|
||||
def SetupForwards(self):
|
||||
for forward in self.Forwards:
|
||||
forwardDomain = forward.get('forward_domain', '127.0.0.1')
|
||||
forwardPort = forward.get('forward_port')
|
||||
targetPort = forward.get('target_port')
|
||||
mode = forward.get('mode', 'tcp').upper()
|
||||
if not all([forwardPort, targetPort]):
|
||||
print("Invalid forward configuration, skipping")
|
||||
continue
|
||||
self.SendToServer({
|
||||
'type': 'forward_request',
|
||||
'forward_domain': forwardDomain,
|
||||
'forward_port': forwardPort,
|
||||
'target_port': targetPort,
|
||||
'mode': mode
|
||||
})
|
||||
|
||||
def ReceiveFromServer(self):
|
||||
while self.Running and self.ServerSocket:
|
||||
try:
|
||||
self.ServerSocket.settimeout(1)
|
||||
data = self.ServerSocket.recv(4096)
|
||||
if not data:
|
||||
print("Server disconnected")
|
||||
self.Running = False
|
||||
break
|
||||
self.Buffer += data
|
||||
self.ProcessBuffer()
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Server communication error: {e}")
|
||||
traceback.print_exc()
|
||||
self.Running = False
|
||||
break
|
||||
|
||||
def ProcessBuffer(self):
|
||||
while self.MessageSeparator in self.Buffer:
|
||||
msgEnd = self.Buffer.index(self.MessageSeparator)
|
||||
messageData = self.Buffer[:msgEnd]
|
||||
self.Buffer = self.Buffer[msgEnd + len(self.MessageSeparator):]
|
||||
try:
|
||||
message = json.loads(messageData.decode('utf-8'))
|
||||
self.ProcessServerMessage(message)
|
||||
except json.JSONDecodeError:
|
||||
print("Received invalid JSON from server")
|
||||
except Exception as e:
|
||||
print(f"Error processing server data: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def ProcessServerMessage(self, message):
|
||||
if message.get('type') == 'forward_response':
|
||||
self.HandleForwardResponse(message)
|
||||
elif message.get('type') == 'new_connection':
|
||||
self.HandleNewConnection(message)
|
||||
elif message.get('type') == 'data':
|
||||
self.HandleData(message)
|
||||
elif message.get('type') == 'close_connection':
|
||||
self.HandleCloseConnection(message)
|
||||
elif message.get('type') == 'error':
|
||||
print(f"Server error: {message.get('message')}")
|
||||
|
||||
def HandleForwardResponse(self, message):
|
||||
if message.get('success'):
|
||||
forwardId = message.get('forward_id')
|
||||
targetPort = message.get('target_port')
|
||||
forwardConfig = next((f for f in self.Forwards if f.get('target_port') == targetPort), None)
|
||||
if forwardConfig and forwardId:
|
||||
with self.Lock:
|
||||
self.ForwardMap[forwardId] = {
|
||||
'config': forwardConfig,
|
||||
'connections': {}
|
||||
}
|
||||
print(f"Forward established: {forwardId}")
|
||||
else:
|
||||
print(f"Received forward response for unknown target port {targetPort}")
|
||||
else:
|
||||
print(f"Forward request failed: {message.get('message')}")
|
||||
|
||||
def HandleNewConnection(self, message):
|
||||
forwardId = message.get('forward_id')
|
||||
connId = message.get('conn_id')
|
||||
if not all([forwardId, connId]):
|
||||
return
|
||||
with self.Lock:
|
||||
if forwardId not in self.ForwardMap:
|
||||
print(f"Received connection for unknown forward {forwardId}")
|
||||
return
|
||||
forwardData = self.ForwardMap[forwardId]
|
||||
config = forwardData['config']
|
||||
try:
|
||||
if config.get('mode', 'tcp').upper() == 'TCP':
|
||||
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
conn.connect((config['forward_domain'], config['forward_port']))
|
||||
with self.Lock:
|
||||
forwardData['connections'][connId] = conn
|
||||
self.ConnectionMap[connId] = forwardId
|
||||
threading.Thread(target=self.ForwardToServer, args=(forwardId, connId, conn), daemon=True).start()
|
||||
print(f"Established connection {connId} for forward {forwardId}")
|
||||
else:
|
||||
print(f"Unsupported mode for forward {forwardId}")
|
||||
except Exception as e:
|
||||
print(f"Error establishing connection for {forwardId}: {e}")
|
||||
traceback.print_exc()
|
||||
self.SendToServer({
|
||||
'type': 'close_connection',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId
|
||||
})
|
||||
|
||||
def ForwardToServer(self, forwardId, connId, conn):
|
||||
try:
|
||||
while self.Running:
|
||||
conn.settimeout(1)
|
||||
try:
|
||||
with self.Lock:
|
||||
if connId not in self.ConnectionMap or self.ConnectionMap[connId] != forwardId:
|
||||
break
|
||||
data = conn.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
self.SendToServer({
|
||||
'type': 'data',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId,
|
||||
'data': data.hex()
|
||||
})
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Forward to server error: {e}")
|
||||
traceback.print_exc()
|
||||
break
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
with self.Lock:
|
||||
if forwardId in self.ForwardMap and connId in self.ForwardMap[forwardId]['connections']:
|
||||
del self.ForwardMap[forwardId]['connections'][connId]
|
||||
if connId in self.ConnectionMap:
|
||||
del self.ConnectionMap[connId]
|
||||
self.SendToServer({
|
||||
'type': 'close_connection',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId
|
||||
})
|
||||
print(f"Closed connection {connId} for forward {forwardId}")
|
||||
|
||||
def HandleData(self, message):
|
||||
forwardId = message.get('forward_id')
|
||||
connId = message.get('conn_id')
|
||||
dataHex = message.get('data')
|
||||
if not all([forwardId, connId, dataHex]):
|
||||
return
|
||||
try:
|
||||
data = bytes.fromhex(dataHex)
|
||||
with self.Lock:
|
||||
if forwardId not in self.ForwardMap or connId not in self.ForwardMap[forwardId]['connections']:
|
||||
print(f"Received data for unknown connection {connId}")
|
||||
return
|
||||
conn = self.ForwardMap[forwardId]['connections'][connId]
|
||||
conn.sendall(data)
|
||||
except Exception as e:
|
||||
print(f"Data handling error: {e}")
|
||||
traceback.print_exc()
|
||||
self.SendToServer({
|
||||
'type': 'close_connection',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId
|
||||
})
|
||||
|
||||
def HandleCloseConnection(self, message):
|
||||
forwardId = message.get('forward_id')
|
||||
connId = message.get('conn_id')
|
||||
with self.Lock:
|
||||
if forwardId in self.ForwardMap and connId in self.ForwardMap[forwardId]['connections']:
|
||||
try:
|
||||
self.ForwardMap[forwardId]['connections'][connId].close()
|
||||
except:
|
||||
pass
|
||||
del self.ForwardMap[forwardId]['connections'][connId]
|
||||
if connId in self.ConnectionMap:
|
||||
del self.ConnectionMap[connId]
|
||||
print(f"Connection {connId} for forward {forwardId} closed by server")
|
||||
|
||||
def SendToServer(self, message):
|
||||
if not self.ServerSocket or not self.Running:
|
||||
return
|
||||
try:
|
||||
data = json.dumps(message).encode('utf-8') + self.MessageSeparator
|
||||
self.ServerSocket.sendall(data)
|
||||
except Exception as e:
|
||||
print(f"Error sending to server: {e}")
|
||||
traceback.print_exc()
|
||||
self.Running = False
|
||||
|
||||
def main():
|
||||
config = {
|
||||
"ServerDomain": "127.0.0.1",
|
||||
"ServerPort": 5000,
|
||||
"Key": "07A36AEF1907843",
|
||||
"Forwards": [
|
||||
{
|
||||
"forward_domain": "127.0.0.1",
|
||||
"forward_port": 36667,
|
||||
"target_port": 5002,
|
||||
"mode": "TCP"
|
||||
}
|
||||
]
|
||||
}
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
with open(sys.argv[1], 'r') as f:
|
||||
config.update(json.load(f))
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
traceback.print_exc()
|
||||
client = PortForwardClient(
|
||||
ServerDomain=config["ServerDomain"],
|
||||
ServerPort=int(config["ServerPort"]),
|
||||
Forwards=config["Forwards"],
|
||||
Key=config["Key"]
|
||||
)
|
||||
client.Start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
388
readme.md
Normal file
388
readme.md
Normal file
@@ -0,0 +1,388 @@
|
||||
<p align="center">
|
||||
<img src="https://avatars.githubusercontent.com/u/193612261?v=4" width="270px" />
|
||||
<p align="center">PyFrp</p>
|
||||
<p align="center">Frp (Fast Reverse Proxy) implemented in Python</p>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
> **This project is only in its early stages, and the documentation is currently being improved. Thank you for using this project. If possible, please give it a star ♥**
|
||||
|
||||
# PyFrp
|
||||
|
||||
## 🗡 Brief Introduction
|
||||
The original Frp (https://github.com/Fatedier/frp/) is somewhat bloated in size, while PyFrp is a simplified Python-based implementation with a smaller footprint and fewer features.
|
||||
|
||||
### Comparison with the Original Frp
|
||||
| Feature| PyFrp | Frp|
|
||||
|:---------------:|:-----:|:----:|
|
||||
| Quick Setup| ✅| ✅|
|
||||
| Size| ✅| ❌|
|
||||
| SSL Support| ❌| ✅|
|
||||
| Embeddable| ✅| ✅❌ |
|
||||
| Documentation| ✅| ✅|
|
||||
| TCP Support| ✅| ✅|
|
||||
| UDP Support| ❌| ✅|
|
||||
| HTTP/HTTPS Support| ❌| ✅|
|
||||
|
||||
Choose based on your needs:
|
||||
- If you prioritize **quick setup, small size, simplicity, and embeddability** (e.g., integrating into your Python project), PyFrp is a great choice.
|
||||
- For **production deployments**, Frp remains the better option for now.
|
||||
|
||||
In the future, we aim to enhance PyFrp's functionality, add more configuration options, and potentially support additional protocols (e.g., HTTP/HTTPS).
|
||||
|
||||
We’re just getting started and would greatly appreciate your support—**a ⭐ Star** would mean a lot to us!
|
||||
|
||||
> Note: **HTTPS is not yet supported**. We may add this later, and contributions are welcome!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
The only third-party library you need is `pycryptodome`. Install it with:
|
||||
```bash
|
||||
pip install pycryptodome
|
||||
```
|
||||
While any version of `pycryptodome` should work, we recommend **v3.22.0** if you encounter issues:
|
||||
```bash
|
||||
pip install pycryptodome==3.22.0
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
#### Server (`server_config.json`):
|
||||
```json
|
||||
{
|
||||
"InternalDataPort": 5000, // PyFrp server data port
|
||||
"AllowedPortRange": "5001-5500", // Allowed port range
|
||||
"MaxPortsPerClient": 5, // Max ports per client
|
||||
"Key": "07A36AEF1907843" // Authentication key
|
||||
}
|
||||
```
|
||||
|
||||
#### Client (`client_config.json`):
|
||||
```json
|
||||
{
|
||||
"ServerDomain": "127.0.0.1", // PyFrp server address
|
||||
"ServerPort": 5000, // PyFrp server port
|
||||
"Key": "07A36AEF1907843", // Authentication key
|
||||
"Forwards": [ // Port mappings
|
||||
{
|
||||
"forward_domain": "127.0.0.1", // Local host
|
||||
"forward_port": 36667, // Local port
|
||||
"target_port": 5002, // Target port
|
||||
"mode": "TCP" // Protocol (TCP only)
|
||||
}
|
||||
// Add more mappings as needed
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Running PyFrp
|
||||
|
||||
#### Start the server:
|
||||
```bash
|
||||
python server.py server_config.json
|
||||
```
|
||||
|
||||
#### Start the client:
|
||||
```bash
|
||||
python client.py client_config.json
|
||||
```
|
||||
|
||||
You can also modify the default configuration directly in the source code.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Example
|
||||
|
||||
### Example: Exposing a Local HTTP Server
|
||||
|
||||
1. **Start a local HTTP server**:
|
||||
```bash
|
||||
python -m http.server 36667
|
||||
```
|
||||
|
||||
2. **Configure PyFrp client** (`client_config.json`):
|
||||
```json
|
||||
{
|
||||
"ServerDomain": "127.0.0.1",
|
||||
"ServerPort": 5000,
|
||||
"Key": "07A36AEF1907843",
|
||||
"Forwards": [
|
||||
{
|
||||
"forward_domain": "127.0.0.1",
|
||||
"forward_port": 36667,
|
||||
"target_port": 5002,
|
||||
"mode": "TCP"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. **Start PyFrp server and client**:
|
||||
```bash
|
||||
# In terminal 1
|
||||
python server.py
|
||||
|
||||
# In terminal 2
|
||||
python client.py
|
||||
```
|
||||
|
||||
4. **Access the local server through the proxy**:
|
||||
```bash
|
||||
curl http://localhost:5002
|
||||
```
|
||||
|
||||
You should see the HTTP response from your local server, indicating that the port forwarding is working correctly.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Limitations
|
||||
|
||||
- **Only TCP protocol is supported** (UDP, HTTP, HTTPS are not supported)
|
||||
- **No SSL encryption** for data transmission
|
||||
- **Simple authentication mechanism** (fixed key)
|
||||
- **No automatic reconnection** after network interruption
|
||||
- **Limited error handling** and logging
|
||||
- **Performance limitations** due to JSON and hex encoding for data transfer
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ How It Works
|
||||
|
||||
### Server-side (server.py)
|
||||
1. Listens for client connections on the internal data port
|
||||
2. Handles client authentication
|
||||
3. Creates and manages port forwarding services
|
||||
4. Forwards data between external connections and clients
|
||||
|
||||
### Client-side (client.py)
|
||||
1. Connects to the server and authenticates
|
||||
2. Sends port forwarding configuration
|
||||
3. Handles connections to local services
|
||||
4. Forwards data between the server and local services
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Here's how you can help:
|
||||
|
||||
1. **Report bugs** by opening an issue
|
||||
2. **Suggest features** by opening an issue
|
||||
3. **Submit pull requests** with bug fixes or enhancements
|
||||
|
||||
### Development Guidelines
|
||||
- Follow the existing code style
|
||||
- Add comments for complex code
|
||||
- Test your changes thoroughly
|
||||
- Update documentation as needed
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact Us
|
||||
- 📧 Email: wyt18222152539wyt@163.com
|
||||
- 🌐 Website: [Galaxy Vastar Software Studio](https://www.gvsds.com)
|
||||
- 📱 WeChat: GVSADS
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License. See the LICENSE file for details.
|
||||
|
||||
---
|
||||
|
||||
## 🗡 简单介绍
|
||||
原本的 Frp https://github.com/Fatedier/frp/ 体积略显臃肿,而 PyFrp 则是一个基于 Python 实现的简单版本,体积更小,功能也更简单。
|
||||
|
||||
我们 和 原版 Frp 相比
|
||||
| 功能 | PyFrp | Frp |
|
||||
|:-:|:-:|:-:|
|
||||
| 快速配置 | ✅ | ✅ |
|
||||
| 体积 | ✅ | ❌ |
|
||||
| SSL 功能 | ❌ | ✅ |
|
||||
| 嵌入式 | ✅ | ✅❌ |
|
||||
| 文档 | ✅ | ✅ |
|
||||
| TCP 支持 | ✅ | ✅ |
|
||||
| UDP 支持 | ❌ | ✅ |
|
||||
| HTTP/HTTPS 支持 | ❌ | ✅ |
|
||||
|
||||
请您根据您的需要选择使用哪个,
|
||||
- 如果您需要快速配置,体积小,功能简单,嵌入式,集成到您自己的 Python 项目中,那么 PyFrp 是一个不错的选择。
|
||||
- 如果您需要部署在生产环境中,暂时不要选择 PyFrp,Frp 是一个更好的选择。
|
||||
|
||||
日后,我们以 Frp 为目标,完善其功能,添加更多的配置项,同时也会考虑添加更多的协议支持,如 http、https 等。
|
||||
|
||||
我们其实也才起步,仍然需要您的支持,如果可以,还请您点一个 Star ⭐,这将是对我们最大的支持。
|
||||
|
||||
> 注意,我们暂时还不支持 https 协议。稍后如果有时间,我们可能会考虑支持,如果您已经帮我们支持,随时欢迎您提交。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 先决条件
|
||||
|
||||
你唯一需要下载的第三方库是 `pycryptodome`,使用以下命令安装:
|
||||
```bash
|
||||
pip install pycryptodome
|
||||
```
|
||||
我们推荐使用 **v3.22.0** 版本:
|
||||
```bash
|
||||
pip install pycryptodome==3.22.0
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
#### 服务器端配置 (`server_config.json`):
|
||||
```json
|
||||
{
|
||||
"InternalDataPort": 5000, // PyFrp 服务器端数据端口
|
||||
"AllowedPortRange": "5001-5500", // 允许的端口范围
|
||||
"MaxPortsPerClient": 5, // 每个客户端最大端口数
|
||||
"Key": "07A36AEF1907843" // 认证密钥
|
||||
}
|
||||
```
|
||||
|
||||
#### 客户端配置 (`client_config.json`):
|
||||
```json
|
||||
{
|
||||
"ServerDomain": "127.0.0.1", // PyFrp 服务器端主机地址
|
||||
"ServerPort": 5000, // PyFrp 服务器端端口
|
||||
"Key": "07A36AEF1907843", // 认证密钥
|
||||
"Forwards": [ // 端口映射配置
|
||||
{
|
||||
"forward_domain": "127.0.0.1", // 本地主机地址
|
||||
"forward_port": 36667, // 本地端口
|
||||
"target_port": 5002, // 目标端口
|
||||
"mode": "TCP" // 传输模式(仅支持TCP)
|
||||
}
|
||||
// 你可以在这里输入更多的端口映射配置
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 运行 PyFrp
|
||||
|
||||
#### 启动服务器:
|
||||
```bash
|
||||
python server.py server_config.json
|
||||
```
|
||||
|
||||
#### 启动客户端:
|
||||
```bash
|
||||
python client.py client_config.json
|
||||
```
|
||||
|
||||
你也可以直接修改源代码中的默认配置。
|
||||
|
||||
---
|
||||
|
||||
## <20> 使用示例
|
||||
|
||||
### 示例:暴露本地 HTTP 服务器
|
||||
|
||||
1. **启动本地 HTTP 服务器**:
|
||||
```bash
|
||||
python -m http.server 36667
|
||||
```
|
||||
|
||||
2. **配置 PyFrp 客户端** (`client_config.json`):
|
||||
```json
|
||||
{
|
||||
"ServerDomain": "127.0.0.1",
|
||||
"ServerPort": 5000,
|
||||
"Key": "07A36AEF1907843",
|
||||
"Forwards": [
|
||||
{
|
||||
"forward_domain": "127.0.0.1",
|
||||
"forward_port": 36667,
|
||||
"target_port": 5002,
|
||||
"mode": "TCP"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. **启动 PyFrp 服务器和客户端**:
|
||||
```bash
|
||||
# 在终端 1 中
|
||||
python server.py
|
||||
|
||||
# 在终端 2 中
|
||||
python client.py
|
||||
```
|
||||
|
||||
4. **通过代理访问本地服务器**:
|
||||
```bash
|
||||
curl http://localhost:5002
|
||||
```
|
||||
|
||||
你应该会看到来自本地服务器的 HTTP 响应,表明端口转发工作正常。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 限制
|
||||
|
||||
- **仅支持 TCP 协议**(不支持 UDP、HTTP、HTTPS)
|
||||
- **数据传输无 SSL 加密**
|
||||
- **简单的认证机制**(固定密钥)
|
||||
- **网络中断后无自动重连**
|
||||
- **有限的错误处理**和日志记录
|
||||
- **性能限制**(由于使用 JSON 和 hex 编码传输数据)
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 工作原理
|
||||
|
||||
### 服务器端 (server.py)
|
||||
1. 在内部数据端口上监听客户端连接
|
||||
2. 处理客户端认证
|
||||
3. 创建和管理端口转发服务
|
||||
4. 在外部连接和客户端之间转发数据
|
||||
|
||||
### 客户端 (client.py)
|
||||
1. 连接到服务器并进行认证
|
||||
2. 发送端口转发配置
|
||||
3. 处理到本地服务的连接
|
||||
4. 在服务器和本地服务之间转发数据
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎贡献!你可以通过以下方式帮助我们:
|
||||
|
||||
1. **报告错误**:通过打开 issue
|
||||
2. **建议功能**:通过打开 issue
|
||||
3. **提交代码**:通过 pull request 提交 bug 修复或增强功能
|
||||
|
||||
### 开发指南
|
||||
- 遵循现有的代码风格
|
||||
- 为复杂代码添加注释
|
||||
- 彻底测试你的更改
|
||||
- 根据需要更新文档
|
||||
|
||||
---
|
||||
|
||||
## <20>📞 联系我们
|
||||
- 📧 Email: wyt18222152539wyt@163.com
|
||||
- 🌐 官网: [银河万通软件开发工作室](https://www.gvsds.com)
|
||||
- 📱 微信: GVSADS
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 MIT 许可证。详情请参阅 LICENSE 文件。
|
||||
343
server.py
Normal file
343
server.py
Normal file
@@ -0,0 +1,343 @@
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import traceback
|
||||
import sys
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
class PortForwardServer:
|
||||
def __init__(self, InternalDataPort=5000, AllowedPortRange="5001-5500", MaxPortsPerClient=5, Key="07A36AEF1907843"):
|
||||
self.InternalDataPort = InternalDataPort
|
||||
self.AllowedPortRange = AllowedPortRange
|
||||
self.MaxPortsPerClient = MaxPortsPerClient
|
||||
self.Key = Key
|
||||
self.ParsePortRange()
|
||||
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.Clients = {}
|
||||
self.ClientLocks = defaultdict(threading.Lock)
|
||||
self.ForwardMap = {}
|
||||
self.ForwardLocks = defaultdict(threading.Lock)
|
||||
self.Running = True
|
||||
self.MessageSeparator = b'|||'
|
||||
|
||||
def ParsePortRange(self):
|
||||
match = re.match(r'^(\d+)-(\d+)$', self.AllowedPortRange)
|
||||
if not match:
|
||||
raise ValueError("Invalid port range format")
|
||||
self.MinPort = int(match.group(1))
|
||||
self.MaxPort = int(match.group(2))
|
||||
if self.MinPort >= self.MaxPort or self.MinPort < 1 or self.MaxPort > 65535:
|
||||
raise ValueError("Invalid port range values")
|
||||
|
||||
def IsPortAllowed(self, port):
|
||||
return self.MinPort <= port <= self.MaxPort
|
||||
|
||||
def Start(self):
|
||||
try:
|
||||
self.ServerSocket.bind(('0.0.0.0', self.InternalDataPort))
|
||||
self.ServerSocket.listen(5)
|
||||
print(f"Server started on port {self.InternalDataPort}")
|
||||
acceptThread = threading.Thread(target=self.AcceptClients, daemon=True)
|
||||
acceptThread.start()
|
||||
while self.Running:
|
||||
cmd = input("Enter 'exit' to stop server: ")
|
||||
if cmd.lower() == 'exit':
|
||||
self.Running = False
|
||||
break
|
||||
self.Stop()
|
||||
except Exception as e:
|
||||
print(f"Server start error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def Stop(self):
|
||||
self.Running = False
|
||||
self.ServerSocket.close()
|
||||
for clientId, clientData in self.Clients.items():
|
||||
with self.ClientLocks[clientId]:
|
||||
if clientData['socket']:
|
||||
try:
|
||||
clientData['socket'].close()
|
||||
except:
|
||||
pass
|
||||
with self.ForwardLocks[clientId]:
|
||||
for forwardId, forwardData in clientData['forwards'].items():
|
||||
try:
|
||||
forwardData['server'].close()
|
||||
except:
|
||||
pass
|
||||
print("Server stopped")
|
||||
|
||||
def AcceptClients(self):
|
||||
while self.Running:
|
||||
try:
|
||||
clientSocket, addr = self.ServerSocket.accept()
|
||||
print(f"New client connection from {addr}")
|
||||
clientThread = threading.Thread(target=self.HandleClient, args=(clientSocket, addr), daemon=True)
|
||||
clientThread.start()
|
||||
except Exception as e:
|
||||
if self.Running:
|
||||
print(f"Accept error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def HandleClient(self, clientSocket, addr):
|
||||
clientId = f"{addr[0]}:{addr[1]}"
|
||||
self.Clients[clientId] = {'socket': clientSocket, 'forwards': {}, 'addr': addr, 'buffer': b''}
|
||||
try:
|
||||
while self.Running:
|
||||
clientSocket.settimeout(30)
|
||||
try:
|
||||
data = clientSocket.recv(4096)
|
||||
if not data:
|
||||
print(f"Client {clientId} disconnected")
|
||||
break
|
||||
self.Clients[clientId]['buffer'] += data
|
||||
self.ProcessBuffer(clientId)
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Client communication error: {e}")
|
||||
traceback.print_exc()
|
||||
break
|
||||
finally:
|
||||
with self.ClientLocks[clientId]:
|
||||
if clientId in self.Clients:
|
||||
del self.Clients[clientId]
|
||||
with self.ForwardLocks[clientId]:
|
||||
for forwardId in list(self.ForwardMap.keys()):
|
||||
if forwardId.startswith(clientId):
|
||||
del self.ForwardMap[forwardId]
|
||||
try:
|
||||
clientSocket.close()
|
||||
except:
|
||||
pass
|
||||
print(f"Client {clientId} handler cleaned up")
|
||||
|
||||
def ProcessBuffer(self, clientId):
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData:
|
||||
return
|
||||
while self.MessageSeparator in clientData['buffer']:
|
||||
msgEnd = clientData['buffer'].index(self.MessageSeparator)
|
||||
messageData = clientData['buffer'][:msgEnd]
|
||||
clientData['buffer'] = clientData['buffer'][msgEnd + len(self.MessageSeparator):]
|
||||
try:
|
||||
message = json.loads(messageData.decode('utf-8'))
|
||||
self.ProcessClientMessage(clientId, message)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Invalid JSON from client {clientId}")
|
||||
self.SendToClient(clientId, {'type': 'error', 'message': 'Invalid JSON'})
|
||||
except Exception as e:
|
||||
print(f"Error processing message: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def ProcessClientMessage(self, clientId, message):
|
||||
if message.get('type') == 'auth':
|
||||
self.HandleAuth(clientId, message)
|
||||
elif message.get('type') == 'forward_request':
|
||||
self.HandleForwardRequest(clientId, message)
|
||||
elif message.get('type') == 'data':
|
||||
self.HandleData(clientId, message)
|
||||
elif message.get('type') == 'close_forward':
|
||||
self.HandleCloseForward(clientId, message)
|
||||
else:
|
||||
self.SendToClient(clientId, {'type': 'error', 'message': 'Unknown message type'})
|
||||
|
||||
def HandleAuth(self, clientId, message):
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData:
|
||||
return
|
||||
if message.get('key') == self.Key:
|
||||
clientData['authenticated'] = True
|
||||
self.SendToClient(clientId, {'type': 'auth_response', 'success': True})
|
||||
print(f"Client {clientId} authenticated successfully")
|
||||
else:
|
||||
self.SendToClient(clientId, {'type': 'auth_response', 'success': False, 'message': 'Invalid key'})
|
||||
clientData['socket'].close()
|
||||
print(f"Client {clientId} failed authentication")
|
||||
|
||||
def HandleForwardRequest(self, clientId, message):
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData or not clientData.get('authenticated', False):
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Not authenticated'})
|
||||
return
|
||||
with self.ClientLocks[clientId]:
|
||||
if len(clientData['forwards']) >= self.MaxPortsPerClient:
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Max ports per client reached'})
|
||||
return
|
||||
targetPort = message.get('target_port')
|
||||
mode = message.get('mode', 'tcp').upper()
|
||||
if not self.IsPortAllowed(targetPort):
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Target port not allowed'})
|
||||
return
|
||||
forwardId = f"{clientId}:{targetPort}"
|
||||
if forwardId in self.ForwardMap:
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Port already in use'})
|
||||
return
|
||||
try:
|
||||
if mode == 'TCP':
|
||||
forwardServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
forwardServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
forwardServer.bind(('0.0.0.0', targetPort))
|
||||
forwardServer.listen(5)
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData['forwards'][forwardId] = {'server': forwardServer, 'mode': mode, 'connections': {}}
|
||||
with self.ForwardLocks[clientId]:
|
||||
self.ForwardMap[forwardId] = clientId
|
||||
threading.Thread(target=self.AcceptForwardConnections, args=(clientId, forwardId, forwardServer), daemon=True).start()
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': True, 'target_port': targetPort, 'forward_id': forwardId})
|
||||
print(f"Forward created: {forwardId}")
|
||||
else:
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': 'Unsupported mode'})
|
||||
except Exception as e:
|
||||
print(f"Forward creation error: {e}")
|
||||
traceback.print_exc()
|
||||
self.SendToClient(clientId, {'type': 'forward_response', 'success': False, 'message': str(e)})
|
||||
|
||||
def AcceptForwardConnections(self, clientId, forwardId, forwardServer):
|
||||
try:
|
||||
while self.Running and forwardId in self.ForwardMap and self.ForwardMap[forwardId] == clientId:
|
||||
forwardServer.settimeout(1)
|
||||
try:
|
||||
conn, addr = forwardServer.accept()
|
||||
connId = f"{addr[0]}:{addr[1]}"
|
||||
print(f"New connection to forward {forwardId} from {connId}")
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData or forwardId not in clientData['forwards']:
|
||||
conn.close()
|
||||
continue
|
||||
clientData['forwards'][forwardId]['connections'][connId] = conn
|
||||
self.SendToClient(clientId, {
|
||||
'type': 'new_connection',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId
|
||||
})
|
||||
threading.Thread(target=self.ForwardToClient, args=(clientId, forwardId, connId, conn), daemon=True).start()
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Forward accept error: {e}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
try:
|
||||
forwardServer.close()
|
||||
except:
|
||||
pass
|
||||
print(f"Forward listener {forwardId} stopped")
|
||||
|
||||
def ForwardToClient(self, clientId, forwardId, connId, conn):
|
||||
try:
|
||||
while self.Running:
|
||||
conn.settimeout(1)
|
||||
try:
|
||||
data = conn.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
self.SendToClient(clientId, {
|
||||
'type': 'data',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId,
|
||||
'data': data.hex()
|
||||
})
|
||||
except socket.timeout:
|
||||
if not self.Running or clientId not in self.Clients:
|
||||
break
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Forward to client error: {e}")
|
||||
traceback.print_exc()
|
||||
break
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData = self.Clients.get(clientId)
|
||||
if clientData and forwardId in clientData['forwards']:
|
||||
if connId in clientData['forwards'][forwardId]['connections']:
|
||||
del clientData['forwards'][forwardId]['connections'][connId]
|
||||
self.SendToClient(clientId, {
|
||||
'type': 'close_connection',
|
||||
'forward_id': forwardId,
|
||||
'conn_id': connId
|
||||
})
|
||||
print(f"Connection {connId} to forward {forwardId} closed")
|
||||
|
||||
def HandleData(self, clientId, message):
|
||||
forwardId = message.get('forward_id')
|
||||
connId = message.get('conn_id')
|
||||
dataHex = message.get('data')
|
||||
if not all([forwardId, connId, dataHex]):
|
||||
return
|
||||
try:
|
||||
data = bytes.fromhex(dataHex)
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData or forwardId not in clientData['forwards']:
|
||||
return
|
||||
forwardData = clientData['forwards'][forwardId]
|
||||
if connId not in forwardData['connections']:
|
||||
return
|
||||
conn = forwardData['connections'][connId]
|
||||
conn.sendall(data)
|
||||
except Exception as e:
|
||||
print(f"Data handling error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def HandleCloseForward(self, clientId, message):
|
||||
forwardId = message.get('forward_id')
|
||||
if not forwardId:
|
||||
return
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData = self.Clients.get(clientId)
|
||||
if clientData and forwardId in clientData['forwards']:
|
||||
try:
|
||||
clientData['forwards'][forwardId]['server'].close()
|
||||
except:
|
||||
pass
|
||||
del clientData['forwards'][forwardId]
|
||||
with self.ForwardLocks[clientId]:
|
||||
if forwardId in self.ForwardMap:
|
||||
del self.ForwardMap[forwardId]
|
||||
print(f"Forward {forwardId} closed by client")
|
||||
|
||||
def SendToClient(self, clientId, message):
|
||||
try:
|
||||
with self.ClientLocks[clientId]:
|
||||
clientData = self.Clients.get(clientId)
|
||||
if not clientData or not clientData['socket']:
|
||||
return
|
||||
clientSocket = clientData['socket']
|
||||
data = json.dumps(message).encode('utf-8') + self.MessageSeparator
|
||||
clientSocket.sendall(data)
|
||||
except Exception as e:
|
||||
print(f"Error sending to client {clientId}: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
def main():
|
||||
config = {
|
||||
"InternalDataPort": 5000,
|
||||
"AllowedPortRange": "5001-5500",
|
||||
"MaxPortsPerClient": 5,
|
||||
"Key": "07A36AEF1907843"
|
||||
}
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
with open(sys.argv[1], 'r') as f:
|
||||
config.update(json.load(f))
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
traceback.print_exc()
|
||||
server = PortForwardServer(
|
||||
InternalDataPort=int(config["InternalDataPort"]),
|
||||
AllowedPortRange=config["AllowedPortRange"],
|
||||
MaxPortsPerClient=int(config["MaxPortsPerClient"]),
|
||||
Key=config["Key"]
|
||||
)
|
||||
server.Start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user