TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
7.2 KiB
199 lines
7.2 KiB
<?php
|
|
|
|
/**
|
|
* SSH tunnel rule and command-line generation.
|
|
*
|
|
* This class deliberately contains no Qt-specific code. The GUI only
|
|
* collects values; validation and SSH semantics belong to TypePHP.
|
|
*/
|
|
class TunnelRule
|
|
{
|
|
public const TYPE_LOCAL = 'local';
|
|
public const TYPE_REMOTE = 'remote';
|
|
public const TYPE_SOCKS5 = 'socks5';
|
|
|
|
public string $id;
|
|
public string $name;
|
|
public string $type;
|
|
public string $sshHost;
|
|
public int $sshPort;
|
|
public string $sshUser;
|
|
public string $identityFile;
|
|
public bool $debug;
|
|
public string $localHost;
|
|
public int $localPort;
|
|
public string $remoteHost;
|
|
public int $remotePort;
|
|
|
|
public function __construct(array $data)
|
|
{
|
|
$this->id = (string) ($data['id'] ?? '');
|
|
$this->name = trim((string) ($data['name'] ?? ''));
|
|
$this->type = (string) ($data['type'] ?? self::TYPE_LOCAL);
|
|
$this->sshHost = trim((string) ($data['ssh_host'] ?? ''));
|
|
$this->sshPort = (int) ($data['ssh_port'] ?? 22);
|
|
$this->sshUser = trim((string) ($data['ssh_user'] ?? ''));
|
|
$this->identityFile = trim((string) ($data['identity_file'] ?? ''));
|
|
$this->debug = (bool) ($data['debug'] ?? false);
|
|
|
|
if (array_key_exists('local_host', $data) || array_key_exists('remote_host', $data)) {
|
|
$this->localHost = trim((string) ($data['local_host'] ?? '127.0.0.1'));
|
|
$this->localPort = (int) ($data['local_port'] ?? 0);
|
|
$this->remoteHost = trim((string) ($data['remote_host'] ?? ''));
|
|
$this->remotePort = (int) ($data['remote_port'] ?? 0);
|
|
} elseif ($this->type === self::TYPE_REMOTE) {
|
|
// Migrate the old -R-oriented bind/target schema to endpoints as
|
|
// users understand them: target was local, bind was remote.
|
|
$this->localHost = trim((string) ($data['target_host'] ?? '127.0.0.1'));
|
|
$this->localPort = (int) ($data['target_port'] ?? 0);
|
|
$this->remoteHost = trim((string) ($data['bind_host'] ?? '127.0.0.1'));
|
|
$this->remotePort = (int) ($data['bind_port'] ?? 0);
|
|
} else {
|
|
// Local forwarding and SOCKS used bind as the local endpoint.
|
|
$this->localHost = trim((string) ($data['bind_host'] ?? '127.0.0.1'));
|
|
$this->localPort = (int) ($data['bind_port'] ?? 0);
|
|
$this->remoteHost = trim((string) ($data['target_host'] ?? ''));
|
|
$this->remotePort = (int) ($data['target_port'] ?? 0);
|
|
}
|
|
|
|
if ($this->id === '') {
|
|
$this->id = str_replace('.', '', uniqid('rule_', true));
|
|
}
|
|
$this->validate();
|
|
}
|
|
|
|
public function validate(): void
|
|
{
|
|
if ($this->name === '') {
|
|
throw new InvalidArgumentException('规则名称不能为空');
|
|
}
|
|
if (!in_array($this->type, [self::TYPE_LOCAL, self::TYPE_REMOTE, self::TYPE_SOCKS5], true)) {
|
|
throw new InvalidArgumentException('不支持的隧道类型:' . $this->type);
|
|
}
|
|
if ($this->sshHost === '') {
|
|
throw new InvalidArgumentException('SSH 服务器不能为空');
|
|
}
|
|
if ($this->sshUser === '') {
|
|
throw new InvalidArgumentException('SSH 用户不能为空');
|
|
}
|
|
$this->assertPort($this->sshPort, 'SSH 端口');
|
|
$this->assertPort($this->localPort, '本机端口');
|
|
if ($this->localHost === '') {
|
|
throw new InvalidArgumentException('本机地址不能为空');
|
|
}
|
|
if ($this->type !== self::TYPE_SOCKS5) {
|
|
if ($this->remoteHost === '') {
|
|
throw new InvalidArgumentException('远程地址不能为空');
|
|
}
|
|
$this->assertPort($this->remotePort, '远程端口');
|
|
}
|
|
}
|
|
|
|
private function assertPort(int $port, string $field): void
|
|
{
|
|
if ($port < 1 || $port > 65535) {
|
|
throw new InvalidArgumentException($field . '必须在 1 到 65535 之间');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate arguments for QProcess. No shell is involved, so values such
|
|
* as file names and hosts are passed as individual arguments.
|
|
*/
|
|
public function sshArguments(): array
|
|
{
|
|
$arguments = [
|
|
'-N',
|
|
'-T',
|
|
'-o', 'ExitOnForwardFailure=yes',
|
|
'-o', 'ServerAliveInterval=30',
|
|
'-o', 'ServerAliveCountMax=3',
|
|
'-o', 'BatchMode=yes',
|
|
'-p', (string) $this->sshPort,
|
|
];
|
|
|
|
if ($this->identityFile !== '') {
|
|
$arguments[] = '-i';
|
|
$arguments[] = $this->identityFile;
|
|
}
|
|
|
|
if ($this->debug || $this->type === self::TYPE_SOCKS5) {
|
|
// SOCKS needs DEBUG2 internally so a failed channel can be
|
|
// correlated with its requested destination.
|
|
$arguments[] = '-vv';
|
|
}
|
|
|
|
if ($this->type === self::TYPE_LOCAL) {
|
|
$arguments[] = '-L';
|
|
$arguments[] = $this->formatForwardHost($this->localHost) . ':' . $this->localPort
|
|
. ':' . $this->formatForwardHost($this->remoteHost) . ':' . $this->remotePort;
|
|
} elseif ($this->type === self::TYPE_REMOTE) {
|
|
$arguments[] = '-R';
|
|
$arguments[] = $this->formatForwardHost($this->remoteHost) . ':' . $this->remotePort
|
|
. ':' . $this->formatForwardHost($this->localHost) . ':' . $this->localPort;
|
|
} else {
|
|
$arguments[] = '-D';
|
|
$arguments[] = $this->formatForwardHost($this->localHost) . ':' . $this->localPort;
|
|
}
|
|
|
|
$arguments[] = $this->sshUser . '@' . $this->sshHost;
|
|
return $arguments;
|
|
}
|
|
|
|
private function formatForwardHost(string $host): string
|
|
{
|
|
if (str_contains($host, ':')
|
|
&& !str_starts_with($host, '[')
|
|
&& !str_ends_with($host, ']')) {
|
|
return '[' . $host . ']';
|
|
}
|
|
return $host;
|
|
}
|
|
|
|
public function typeLabel(): string
|
|
{
|
|
if ($this->type === self::TYPE_LOCAL) {
|
|
return '服务器端口 → 本地端口';
|
|
}
|
|
if ($this->type === self::TYPE_REMOTE) {
|
|
return '本地端口 → 服务器端口';
|
|
}
|
|
return '服务器 → 本地 SOCKS5 代理';
|
|
}
|
|
|
|
public function localAddressLabel(): string
|
|
{
|
|
return $this->displayAddress($this->localHost, $this->localPort);
|
|
}
|
|
|
|
public function remoteAddressLabel(): string
|
|
{
|
|
if ($this->type === self::TYPE_SOCKS5) {
|
|
return $this->displayAddress($this->sshHost, $this->sshPort);
|
|
}
|
|
return $this->displayAddress($this->remoteHost, $this->remotePort);
|
|
}
|
|
|
|
private function displayAddress(string $host, int $port): string
|
|
{
|
|
return $this->formatForwardHost($host) . ':' . $port;
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'name' => $this->name,
|
|
'type' => $this->type,
|
|
'ssh_host' => $this->sshHost,
|
|
'ssh_port' => $this->sshPort,
|
|
'ssh_user' => $this->sshUser,
|
|
'identity_file' => $this->identityFile,
|
|
'debug' => $this->debug,
|
|
'local_host' => $this->localHost,
|
|
'local_port' => $this->localPort,
|
|
'remote_host' => $this->remoteHost,
|
|
'remote_port' => $this->remotePort,
|
|
];
|
|
}
|
|
}
|
|
|