压力优化
This commit is contained in:
@@ -24,10 +24,15 @@ use Workerman\Worker;
|
||||
|
||||
final class AgentListener
|
||||
{
|
||||
private const MAX_SEND_BUFFER = 32 * 1024 * 1024;
|
||||
private const BACKPRESSURE_HIGH_WATERMARK = 16 * 1024 * 1024;
|
||||
|
||||
/** @var array<int, FrameParser> */
|
||||
private array $parsers = [];
|
||||
/** @var array<int, string> */
|
||||
private array $connectionNodeIds = [];
|
||||
/** @var array<int, array<string, AsyncTcpConnection>> */
|
||||
private array $pausedTargetsByAgent = [];
|
||||
|
||||
public function __construct(
|
||||
Worker $worker,
|
||||
@@ -46,7 +51,8 @@ final class AgentListener
|
||||
|
||||
private function onConnect(TcpConnection $connection): void
|
||||
{
|
||||
$connection->maxSendBufferSize = 8 * 1024 * 1024;
|
||||
$connection->maxSendBufferSize = self::MAX_SEND_BUFFER;
|
||||
$connection->onBufferDrain = fn (TcpConnection $connection) => $this->resumeTargetsForAgent($connection);
|
||||
$this->parsers[$connection->id] = new FrameParser();
|
||||
}
|
||||
|
||||
@@ -204,6 +210,12 @@ final class AgentListener
|
||||
return;
|
||||
}
|
||||
|
||||
$targetAddress = $this->resolveTargetAddress($host, $port);
|
||||
if ($targetAddress === null) {
|
||||
$this->rejectOpen($agentConnection, $frame, 'dns_resolution_failed', (string)$auth['user_id'], $nodeId, $decision->policyId);
|
||||
return;
|
||||
}
|
||||
|
||||
$session = new TunnelSession(
|
||||
$frame->sessionId,
|
||||
(string)$auth['user_id'],
|
||||
@@ -219,8 +231,8 @@ final class AgentListener
|
||||
$session->state = TunnelSession::OPENING;
|
||||
$this->sessions->add($session);
|
||||
|
||||
$target = new AsyncTcpConnection("tcp://{$host}:{$port}");
|
||||
$target->maxSendBufferSize = 8 * 1024 * 1024;
|
||||
$target = new AsyncTcpConnection($targetAddress);
|
||||
$target->maxSendBufferSize = self::MAX_SEND_BUFFER;
|
||||
$session->target = $target;
|
||||
|
||||
$target->onConnect = function () use ($session, $agentConnection): void {
|
||||
@@ -232,9 +244,17 @@ final class AgentListener
|
||||
};
|
||||
$target->onMessage = function (AsyncTcpConnection $target, string $data) use ($session, $agentConnection): void {
|
||||
$session->bytesTargetToClient += strlen($data);
|
||||
$this->send($agentConnection, new Frame(FrameType::DATA, $session->sessionId, [
|
||||
$sent = $this->send($agentConnection, new Frame(FrameType::DATA, $session->sessionId, [
|
||||
'data_raw' => $data,
|
||||
]));
|
||||
if ($sent === false) {
|
||||
$this->closeSession($session, 'failed', 'agent_buffer_overflow');
|
||||
return;
|
||||
}
|
||||
if ($agentConnection->getSendBufferQueueSize() >= self::BACKPRESSURE_HIGH_WATERMARK) {
|
||||
$target->pauseRecv();
|
||||
$this->pausedTargetsByAgent[$agentConnection->id][$session->sessionId] = $target;
|
||||
}
|
||||
};
|
||||
$target->onClose = fn () => $this->closeSession($session, 'closed', null);
|
||||
$target->onError = fn () => $this->failOpenSession($agentConnection, $session, 'target_connection_refused');
|
||||
@@ -250,12 +270,28 @@ final class AgentListener
|
||||
}
|
||||
|
||||
$session->bytesClientToTarget += strlen($data);
|
||||
$session->target?->send($data);
|
||||
if ($session->target === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sent = $session->target->send($data);
|
||||
if ($sent === false) {
|
||||
$this->closeSession($session, 'failed', 'target_buffer_overflow');
|
||||
return;
|
||||
}
|
||||
if ($session->target->getSendBufferQueueSize() >= self::BACKPRESSURE_HIGH_WATERMARK && $session->agent !== null) {
|
||||
$session->agent->pauseRecv();
|
||||
$session->target->onBufferDrain = function (AsyncTcpConnection $target) use ($session): void {
|
||||
$session->agent?->resumeRecv();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private function failOpenSession(TcpConnection $agentConnection, TunnelSession $session, string $reason): void
|
||||
{
|
||||
$this->send($agentConnection, new Frame(FrameType::OPEN_FAIL, $session->sessionId, ['reason' => $reason]));
|
||||
if ($session->state === TunnelSession::OPENING) {
|
||||
$this->send($agentConnection, new Frame(FrameType::OPEN_FAIL, $session->sessionId, ['reason' => $reason]));
|
||||
}
|
||||
$this->closeSession($session, 'failed', $reason);
|
||||
}
|
||||
|
||||
@@ -298,6 +334,9 @@ final class AgentListener
|
||||
|
||||
$session->state = TunnelSession::CLOSED;
|
||||
$this->sessions->remove($session->sessionId);
|
||||
if ($session->agent !== null) {
|
||||
unset($this->pausedTargetsByAgent[$session->agent->id][$session->sessionId]);
|
||||
}
|
||||
|
||||
if ($session->agent !== null) {
|
||||
$this->send($session->agent, new Frame(FrameType::CLOSE, $session->sessionId, ['reason' => $reason ?? $result]));
|
||||
@@ -329,6 +368,7 @@ final class AgentListener
|
||||
{
|
||||
unset($this->parsers[$connection->id]);
|
||||
unset($this->connectionNodeIds[$connection->id]);
|
||||
unset($this->pausedTargetsByAgent[$connection->id]);
|
||||
$node = $this->nodes->unregisterByConnection($connection);
|
||||
foreach ($this->sessions->all() as $session) {
|
||||
if ($session->agent === $connection) {
|
||||
@@ -351,8 +391,47 @@ final class AgentListener
|
||||
}
|
||||
}
|
||||
|
||||
private function send(TcpConnection $connection, Frame $frame): void
|
||||
private function send(TcpConnection $connection, Frame $frame): bool|null
|
||||
{
|
||||
$connection->send(FrameCodec::encode($frame));
|
||||
return $connection->send(FrameCodec::encode($frame));
|
||||
}
|
||||
|
||||
private function resumeTargetsForAgent(TcpConnection $connection): void
|
||||
{
|
||||
foreach ($this->pausedTargetsByAgent[$connection->id] ?? [] as $sessionId => $target) {
|
||||
$target->resumeRecv();
|
||||
unset($this->pausedTargetsByAgent[$connection->id][$sessionId]);
|
||||
}
|
||||
if (($this->pausedTargetsByAgent[$connection->id] ?? []) === []) {
|
||||
unset($this->pausedTargetsByAgent[$connection->id]);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveTargetAddress(string $host, int $port): ?string
|
||||
{
|
||||
if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
|
||||
return "tcp://{$host}:{$port}";
|
||||
}
|
||||
if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
|
||||
return "tcp://[{$host}]:{$port}";
|
||||
}
|
||||
|
||||
$records = @dns_get_record($host, DNS_A | DNS_AAAA);
|
||||
if ($records === false || $records === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
if (isset($record['ip']) && is_string($record['ip'])) {
|
||||
return "tcp://{$record['ip']}:{$port}";
|
||||
}
|
||||
}
|
||||
foreach ($records as $record) {
|
||||
if (isset($record['ipv6']) && is_string($record['ipv6'])) {
|
||||
return "tcp://[{$record['ipv6']}]:{$port}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user