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.
208 lines
6.5 KiB
208 lines
6.5 KiB
<?php
|
|
|
|
require dirname(__DIR__) . '/app/TunnelRule.php';
|
|
require dirname(__DIR__) . '/app/TunnelRepository.php';
|
|
require dirname(__DIR__) . '/app/SshOutputParser.php';
|
|
|
|
function assert_same(mixed $expected, mixed $actual, string $message): void
|
|
{
|
|
if ($expected !== $actual) {
|
|
throw new RuntimeException(
|
|
$message . "\nexpected: " . var_export($expected, true)
|
|
. "\nactual: " . var_export($actual, true)
|
|
);
|
|
}
|
|
}
|
|
|
|
function make_rule(array $overrides): TunnelRule
|
|
{
|
|
return new TunnelRule(array_merge([
|
|
'name' => 'Example',
|
|
'type' => TunnelRule::TYPE_LOCAL,
|
|
'ssh_host' => 'gateway.example.com',
|
|
'ssh_port' => 22,
|
|
'ssh_user' => 'deploy',
|
|
'identity_file' => '/keys/id_ed25519',
|
|
'local_host' => '127.0.0.1',
|
|
'local_port' => 3307,
|
|
'remote_host' => 'db.internal',
|
|
'remote_port' => 3306,
|
|
], $overrides));
|
|
}
|
|
|
|
$local = make_rule([]);
|
|
assert_same(
|
|
[
|
|
'-N', '-T',
|
|
'-o', 'ExitOnForwardFailure=yes',
|
|
'-o', 'ServerAliveInterval=30',
|
|
'-o', 'ServerAliveCountMax=3',
|
|
'-o', 'BatchMode=yes',
|
|
'-p', '22',
|
|
'-i', '/keys/id_ed25519',
|
|
'-L', '127.0.0.1:3307:db.internal:3306',
|
|
'deploy@gateway.example.com',
|
|
],
|
|
$local->sshArguments(),
|
|
'local forwarding arguments'
|
|
);
|
|
|
|
$remote = make_rule([
|
|
'type' => TunnelRule::TYPE_REMOTE,
|
|
'local_host' => '127.0.0.1',
|
|
'local_port' => 3000,
|
|
'remote_host' => '127.0.0.1',
|
|
'remote_port' => 8080,
|
|
]);
|
|
assert_same(
|
|
'-R',
|
|
$remote->sshArguments()[14],
|
|
'remote forwarding uses -R'
|
|
);
|
|
assert_same(
|
|
'127.0.0.1:8080:127.0.0.1:3000',
|
|
$remote->sshArguments()[15],
|
|
'remote forwarding mapping'
|
|
);
|
|
|
|
$socks = make_rule([
|
|
'type' => TunnelRule::TYPE_SOCKS5,
|
|
'local_port' => 1080,
|
|
'remote_host' => '',
|
|
'remote_port' => 0,
|
|
'identity_file' => '',
|
|
]);
|
|
$socksArguments = $socks->sshArguments();
|
|
assert_same('-vv', $socksArguments[12], 'SOCKS5 enables destination diagnostics');
|
|
assert_same('-D', $socksArguments[13], 'SOCKS5 uses -D');
|
|
assert_same('127.0.0.1:1080', $socksArguments[14], 'SOCKS5 bind address');
|
|
|
|
$outputParser = new SshOutputParser();
|
|
assert_same(
|
|
null,
|
|
$outputParser->parse(
|
|
'socks-rule',
|
|
'debug2: channel 53: dynamic request: socks5 host missing.example port 443 command 1',
|
|
true
|
|
),
|
|
'SOCKS5 destination diagnostic is retained but hidden'
|
|
);
|
|
assert_same(
|
|
'SOCKS5 连接失败:missing.example:443(connect failed: Name or service not known)',
|
|
$outputParser->parse(
|
|
'socks-rule',
|
|
'channel 53: open failed: connect failed: Name or service not known',
|
|
true
|
|
),
|
|
'SOCKS5 failure includes domain and port'
|
|
);
|
|
assert_same(
|
|
null,
|
|
$outputParser->parse(
|
|
'socks-rule',
|
|
'debug2: channel 40: dynamic request: socks5 host 2001:db8::10 port 8443 command 1',
|
|
true
|
|
),
|
|
'IPv6 SOCKS5 destination is retained'
|
|
);
|
|
assert_same(
|
|
'SOCKS5 连接失败:[2001:db8::10]:8443(connect failed: Connection timed out)',
|
|
$outputParser->parse(
|
|
'socks-rule',
|
|
'channel 40: open failed: connect failed: Connection timed out',
|
|
true
|
|
),
|
|
'SOCKS5 failure formats IPv6 endpoint'
|
|
);
|
|
assert_same(
|
|
'channel 9: open failed: connect failed: Connection refused',
|
|
$outputParser->parse(
|
|
'socks-rule',
|
|
'channel 9: open failed: connect failed: Connection refused',
|
|
true
|
|
),
|
|
'unmatched channel failure remains visible'
|
|
);
|
|
assert_same(
|
|
'ordinary ssh error',
|
|
$outputParser->parse('local-rule', 'ordinary ssh error', false),
|
|
'non-SOCKS output remains unchanged'
|
|
);
|
|
assert_same(
|
|
'debug2: channel 7: pre_dynamic: have 12',
|
|
$outputParser->parse(
|
|
'debug-socks-rule',
|
|
'debug2: channel 7: pre_dynamic: have 12',
|
|
true,
|
|
true
|
|
),
|
|
'enabled debug output remains visible'
|
|
);
|
|
|
|
$debugLocal = make_rule(['debug' => true]);
|
|
assert_same(
|
|
'-vv',
|
|
$debugLocal->sshArguments()[14],
|
|
'debug checkbox enables OpenSSH DEBUG2 output for non-SOCKS rules'
|
|
);
|
|
assert_same(true, $debugLocal->toArray()['debug'], 'debug setting is persisted');
|
|
|
|
$ipv6 = make_rule([
|
|
'local_host' => '::1',
|
|
'remote_host' => '2001:db8::10',
|
|
]);
|
|
assert_same(
|
|
'[::1]:3307:[2001:db8::10]:3306',
|
|
$ipv6->sshArguments()[15],
|
|
'IPv6 forwarding hosts use OpenSSH brackets'
|
|
);
|
|
assert_same('[::1]:3307', $ipv6->localAddressLabel(), 'local forwarding local address');
|
|
assert_same('[2001:db8::10]:3306', $ipv6->remoteAddressLabel(), 'local forwarding remote address');
|
|
assert_same('127.0.0.1:3000', $remote->localAddressLabel(), 'remote forwarding local address');
|
|
assert_same('127.0.0.1:8080', $remote->remoteAddressLabel(), 'remote forwarding remote address');
|
|
assert_same('127.0.0.1:1080', $socks->localAddressLabel(), 'SOCKS5 local address');
|
|
assert_same('gateway.example.com:22', $socks->remoteAddressLabel(), 'SOCKS5 gateway address');
|
|
|
|
$legacyRemote = new TunnelRule([
|
|
'name' => 'Legacy remote rule',
|
|
'type' => TunnelRule::TYPE_REMOTE,
|
|
'ssh_host' => 'gateway.example.com',
|
|
'ssh_port' => 22,
|
|
'ssh_user' => 'deploy',
|
|
'bind_host' => '0.0.0.0',
|
|
'bind_port' => 9000,
|
|
'target_host' => '127.0.0.1',
|
|
'target_port' => 9001,
|
|
]);
|
|
assert_same('127.0.0.1:9001', $legacyRemote->localAddressLabel(), 'legacy target migrates to local');
|
|
assert_same('0.0.0.0:9000', $legacyRemote->remoteAddressLabel(), 'legacy bind migrates to remote');
|
|
|
|
$temporary = sys_get_temp_dir() . '/typephp-ssh-tunnel-' . uniqid('', true) . '.json';
|
|
$repository = new TunnelRepository($temporary);
|
|
$repository->create($local);
|
|
assert_same(1, count($repository->all()), 'create');
|
|
assert_same(true, is_file($temporary . '.bak'), 'durable backup created');
|
|
|
|
$updated = make_rule(array_merge($local->toArray(), ['name' => 'Updated']));
|
|
$repository->update($updated);
|
|
assert_same('Updated', $repository->find($local->id)?->name, 'read/update');
|
|
|
|
$repository->delete($local->id);
|
|
assert_same([], $repository->all(), 'delete');
|
|
|
|
file_put_contents($temporary, '{corrupt json');
|
|
assert_same([], $repository->all(), 'corrupt primary recovered from backup');
|
|
assert_same([], json_decode((string) file_get_contents($temporary), true), 'primary restored from backup');
|
|
|
|
unlink($temporary);
|
|
unlink($temporary . '.bak');
|
|
|
|
$invalidRejected = false;
|
|
try {
|
|
make_rule(['local_port' => 70000]);
|
|
} catch (InvalidArgumentException) {
|
|
$invalidRejected = true;
|
|
}
|
|
assert_same(true, $invalidRejected, 'invalid port rejected');
|
|
|
|
echo "ssh-tunnel domain tests passed\n";
|
|
|