Initial commit of PyFrp project
This commit is contained in:
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()
|
||||
Reference in New Issue
Block a user