<?php
namespace app\process;
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Connection\AsyncTcpConnection;
use support\Redis;
class Proxy
{
public static function log($msg)
{
echo "[Proxy] " . date('H:i:s') . " $msg\n";
}
public function onWorkerStart(Worker $worker)
{
self::log("Worker started, socket={$worker->getSocketName()}, transport={$worker->transport}");
$worker->onMessage = function (TcpConnection $conn, $data) {
// ---------- TCP 缓冲:累积数据直到收到完整的 HTTP 头 ----------
if (!isset($conn->proxyBuffer)) {
$conn->proxyBuffer = '';
}
// 跳过空数据
if (empty(trim($data))) {
self::log("Empty data, ignoring");
return;
}
$conn->proxyBuffer .= $data;
self::log("onMessage, recv=" . strlen($data) . ", buffered=" . strlen($conn->proxyBuffer));
// 取第一行做粗判
$firstLine = strtok($conn->proxyBuffer, "\r\n");
if ($firstLine === false) {
self::log("Incomplete first line, waiting");
return;
}
self::log("First line: " . substr($firstLine, 0, 200));
// ---------- IP 白名单 ----------
$ip = $conn->getRemoteIp();
self::log("Connection from $ip");
if (!Redis::sismember('ip_whitelist', $ip)) {
self::log("IP $ip not in whitelist, closing");
$conn->send("HTTP/1.1 403 Forbidden\r\n\r\n");
$conn->close();
return;
}
// 检查是否收齐 HTTP 头(\r\n\r\n)
$headerEnd = strpos($conn->proxyBuffer, "\r\n\r\n");
// ---------- CONNECT 隧道 ----------
if (stripos($firstLine, 'CONNECT ') === 0) {
if ($headerEnd === false) {
self::log("CONNECT headers incomplete, waiting...");
return;
}
if (!preg_match('#^CONNECT\s+([^:\s]+):(\d+)#i', $conn->proxyBuffer, $m)) {
self::log("Invalid CONNECT request, closing");
$conn->close();
return;
}
$host = $m[1];
$port = (int)$m[2];
self::log("CONNECT -> $host:$port");
// 头之后的额外数据(如 TLS ClientHello 可能合包到达)
$extraData = substr($conn->proxyBuffer, $headerEnd + 4);
$conn->proxyBuffer = '';
$remote = new AsyncTcpConnection('tcp://127.0.0.1:7892');
$remote->onConnect = function ($r) use ($conn, $host, $port, $extraData) {
self::log("Clash connected for CONNECT $host:$port");
$r->send("CONNECT $host:$port HTTP/1.1\r\nHost: $host:$port\r\n\r\n");
$buf = '';
$r->onMessage = function ($r, $data) use ($conn, &$buf, $extraData) {
$buf .= $data;
if (strpos($buf, "\r\n\r\n") !== false) {
$r->onMessage = null;
$conn->send($buf);
if (!empty($extraData)) {
$r->send($extraData);
}
$conn->pipe($r);
$r->pipe($conn);
}
};
};
$remote->onClose = function () use ($conn) { $conn->close(); };
$remote->onError = function ($r, $e, $m) use ($conn) {
self::log("Clash error: [$e] $m");
$conn->send("HTTP/1.1 502 Bad Gateway\r\n\r\n");
$conn->close();
};
$remote->connect();
return;
}
// ---------- HTTP 代理请求 ----------
if (preg_match('#^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(https?://[^\s]+)\s+HTTP#i', $conn->proxyBuffer, $m)) {
if ($headerEnd === false) {
self::log("HTTP headers incomplete, waiting...");
return;
}
$buffer = $conn->proxyBuffer;
$conn->proxyBuffer = '';
$parsed = parse_url($m[2]);
$host = $parsed['host'];
$port = $parsed['port'] ?? 80;
$path = ($parsed['path'] ?? '/') . (isset($parsed['query']) ? '?' . $parsed['query'] : '');
self::log("HTTP -> $host:$port $path");
$buffer = preg_replace('#^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+https?://[^\s]+\s+HTTP#i', '$1 ' . $path . ' HTTP', $buffer);
$buffer = preg_replace('/^Proxy-.*\r?\n/mi', '', $buffer);
$remote = new AsyncTcpConnection('tcp://127.0.0.1:7892');
$remote->onConnect = function ($r) use ($buffer) { $r->send($buffer); };
$remote->onMessage = function ($r, $data) use ($conn) { $conn->send($data); };
$remote->onClose = function () use ($conn) { $conn->close(); };
$remote->onError = function ($r, $e, $m) use ($conn) {
self::log("Clash error: [$e] $m");
$conn->send("HTTP/1.1 502 Bad Gateway\r\n\r\n");
$conn->close();
};
$remote->connect();
$conn->pipe($remote);
return;
}
// ---------- 无法识别的请求 ----------
self::log("Unrecognized request, raw: " . bin2hex(substr($conn->proxyBuffer, 0, 50)));
$conn->proxyBuffer = '';
$conn->close();
};
$worker->onClose = function (TcpConnection $conn) {
self::log("Connection closed from {$conn->getRemoteIp()}");
};
}
}
评论 (0)
发表评论