swoole_async_dns_lookup)支援域名地址<?php
class Client {
public $connected = true;
public $data = '';
public $remote = null;
public $status = 0; // 0=握手, 1=請求解析, 2=轉發中
}
class Server {
public $clients = [];
public function start() {
$server = new swoole_server('0.0.0.0', 8388, SWOOLE_BASE, SWOOLE_SOCK_TCP);
$server->set([
'max_conn' => 1000,
'daemonize' => 1,
'reactor_num' => 1,
'worker_num' => 1,
'dispatch_mode' => 2,
'buffer_output_size' => 128 * 1024 * 1024,
'open_cpu_affinity' => 1,
'open_tcp_nodelay' => 1,
'log_file' => 'socks5_server.log',
]);
$server->on('connect', [$this, 'onConnect']);
$server->on('receive', [$this, 'onReceive']);
$server->on('close', [$this, 'onClose']);
$server->start();
}
public function onConnect($server, $fd, $fromID) {
$this->clients[$fd] = new Client();
}
public function onReceive($server, $fd, $fromID, $data) {
$this->clients[$fd]->data .= $data;
$this->parse($server, $fd);
}
public function onClose($server, $fd, $fromID) {
$client = $this->clients[$fd];
$client->connected = false;
if ($client->remote) {
$client->remote->close();
}
}
private function parse($server, $fd) {
$client = $this->clients[$fd];
switch ($client->status) {
case 0: // 協議握手
if (strlen($client->data) >= 2) {
$request = unpack('c*', substr($client->data, 0, 2));
if ($request[1] !== 0x05) {
echo "協議錯誤: {$request[1]}\n";
$server->close($fd); break;
}
$nmethods = $request[2];
if (strlen($client->data) >= 2 + $nmethods) {
$client->data = substr($client->data, 2 + $nmethods);
$server->send($fd, "\x05\x00"); // 無需認證
$client->status = 1;
}
}
break;
case 1: // 請求解析
if (strlen($client->data) < 5) break;
$request = unpack('c*', $client->data);
$aType = $request[4];
if ($aType === 0x03) { // 域名
$domainLen = $request[5];
if (strlen($client->data) < 5 + $domainLen + 2) break;
$domain = substr($client->data, 5, $domainLen);
$port = unpack('n', substr($client->data, 5 + $domainLen, 2))[1];
$client->data = substr($client->data, 5 + $domainLen + 2);
} elseif ($aType === 0x01) { // IPv4
$domain = long2ip(unpack('N', substr($client->data, 4, 4))[1]);
$port = unpack('n', substr($client->data, 8, 2))[1];
$client->data = substr($client->data, 10);
} else {
echo "不支持的 ATYPE: $aType\n";
$server->close($fd); break;
}
// 連接遠程目標
$remote = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$remote->on('connect', function($cli) use ($client, $server, $fd, $remote) {
$server->send($fd, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00"); // 成功響應
$client->status = 2;
$client->remote = $remote;
});
$remote->on('error', function($cli) use ($server, $fd) {
echo "連接遠程失敗\n";
$server->close($fd);
});
$remote->on('receive', function($cli, $data) use ($server, $fd, $client) {
if (!$client->connected) return;
$server->send($fd, $data);
});
$remote->on('close', function($cli) use ($client) {
$client->remote = null;
});
if ($aType === 0x03) {
swoole_async_dns_lookup($domain, function($host, $ip) use ($remote, $port, $server, $fd) {
if (empty($ip)) { $server->close($fd); return; }
$remote->connect($ip, $port);
});
} else {
$remote->connect($domain, $port);
}
break;
case 2: // 數據轉發
if (strlen($client->data) === 0) break;
if (!$client->remote) break;
$sent = $client->remote->send($client->data);
if ($sent === false || $sent < strlen($client->data)) {
echo "發送不完整: len=" . strlen($client->data) . " sent=$sent\n";
$server->close($fd);
}
$client->data = '';
break;
}
}
}
(new Server())->start();
swoole_timer_tick 定時掃描清理shutdown(SHUT_RD/WR) 支援有限,需升級版本或應用層協議心跳SWOOLE_PROCESS 模式,worker_num = CPU 核心數\x05\x00),生產環境需添加用戶名密碼驗證(RFC 1929)# 瀏覽器/工具配置 SOCKS5 代理 127.0.0.1:8388 # 訪問 https://www.oschina.net 正常 # 訪問網易新聞等複雜站點可能因超時/協議細節失敗
代碼已開源,歡迎 PR 完善超時、認證、UDP、多進程等企業級特性。