├── .gitignore ├── map.json ├── conf.json ├── testc.php ├── test.php ├── composer.json ├── LICENSE ├── src ├── Dev │ └── EchoServer.php ├── Client.php ├── Server.php └── Protocols │ └── Websockethack.php ├── README.md ├── composer.lock └── Document-zh_cn.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.vscode/ -------------------------------------------------------------------------------- /map.json: -------------------------------------------------------------------------------- 1 | [["tcp:\/\/tzqsyzx.com:443","tcp:\/\/127.0.0.23:443","tcp"]] -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abc", 3 | "count": "3", 4 | "http.type": "proxy", 5 | "http.param": "tcp:\/\/127.0.0.1:80", 6 | "user": { 7 | "test": { 8 | "password": "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", 9 | "whitelist": [], 10 | "blacklist": [], 11 | "shortcut": [] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /testc.php: -------------------------------------------------------------------------------- 1 | enableSSL(false); 8 | 9 | $client->login("test", "123"); 10 | 11 | // $client->importMap("map.json"); 12 | 13 | $client->map("tcp://tzqsyzx.com:443", "tcp://127.0.0.23:443"); 14 | 15 | // $client->map("someservice", "22346"); 16 | 17 | $client->exportMap("map.json"); 18 | 19 | \Workerman\Worker::runAll(); -------------------------------------------------------------------------------- /test.php: -------------------------------------------------------------------------------- 1 | importConfig('./conf.json'); 10 | 11 | $server->addUser('test', '123'); 12 | 13 | $server->config([ 14 | "name" => "abc", 15 | "count" => "3" 16 | ]); 17 | 18 | // $server->config('http.type', 'proxy'); 19 | // $server->config('http.param', 'http://127.0.0.1/'); 20 | 21 | $server->exportConfig('./conf.json'); 22 | 23 | $server->listen("0.0.0.0:5280"); 24 | 25 | \Workerman\Worker::runAll(); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xtlsoft/oneport", 3 | "description": "An application let you do everything in just a port. A super gateway.", 4 | "type": "library", 5 | "require": { 6 | "php": ">=5.4", 7 | "workerman/workerman": "^3.5" 8 | }, 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Tianle Xu", 13 | "email": "xtlsoft@126.com" 14 | } 15 | ], 16 | "autoload": { 17 | "psr-4": { 18 | "CloudSky\\OnePort\\": "./src/", 19 | "Workerman\\Protocols\\": "./src/Protocols/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 CloudSky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Dev/EchoServer.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | */ 14 | 15 | namespace CloudSky\OnePort\Dev; 16 | 17 | class EchoServer { 18 | 19 | protected $worker = null; 20 | 21 | public function __construct($addr, $name = "test", $count=1){ 22 | 23 | $this->worker = new \Workerman\Worker($addr); 24 | 25 | $this->worker->name = "CloudSky_OnePort_Dev_EchoServer_" . $name; 26 | $this->worker->count = $count; 27 | 28 | $this->serve($this->worker); 29 | 30 | } 31 | 32 | public function getWorker($worker){ 33 | 34 | return $this->worker; 35 | 36 | } 37 | 38 | public function serve($worker){ 39 | 40 | $worker->onWorkerStart = function ($wkr) { 41 | 42 | echo "[Msg][" . date('Y-m-d h:i:s') . "] Worker " . $wkr->name . " StartUp\r\n"; 43 | 44 | }; 45 | 46 | $worker->onConnect = function ($con) { 47 | 48 | $con->send("HELLO FROM SERVER!\r\n"); 49 | echo "[Msg][" . date('Y-m-d h:i:s') . "] Client Connected To " . $con->worker->name . "\r\n"; 50 | 51 | }; 52 | 53 | $worker->onMessage = function ($con, $msg){ 54 | 55 | $con->send("Recv: $msg\r\n"); 56 | echo "[Msg][" . date('Y-m-d h:i:s') . "] Client Sent '$msg' To " . $con->worker->name . "\r\n"; 57 | 58 | }; 59 | 60 | $worker->onClose = function ($con){ 61 | 62 | echo "[Msg][" . date('Y-m-d h:i:s') . "] Client Closed. Server: " . $con->worker->name . "\r\n"; 63 | 64 | }; 65 | 66 | return $this; 67 | 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OnePort 2 | [![license](https://img.shields.io/apm/l/vim-mode.svg)](https://github.com/ourCloudSky/OnePort/blob/master/LICENSE) 3 | 4 | A Super Fast And Good Proxy Written in PHP. 5 | 6 | [![OnePort](https://github.com/ourCloudSky/OnePort/raw/master/docs/logo.png)](https://github.com/ourCloudSky/OnePort) 7 | [![CloudSky](https://avatars0.githubusercontent.com/u/32470726?v=4&s=200)](https://github.com/ourCloudSky) 8 | 9 | ## 视频 Video 10 | https://www.bilibili.com/video/av15186309/ 11 | 12 | ## 文档 Document 13 | https://github.com/ourCloudSky/OnePort-php/blob/master/Document-zh_cn.md 14 | 15 | ## 特性 Feature 16 | - Fast, Responsive, Cross-platform | 快速,响应式,跨平台 17 | - Written on PHP | 使用 PHP 编写 18 | - Allow to set Muiti-User Password | 可以为多个用户分别设置密码 19 | - Allow to encrypt your data | 可以对数据加密传输 20 | - Do more than PortMap, Lighter than PortMap | 比端口映射做得更多,比端口映射更轻快 21 | - Free, Open-Source, Easy-to-use | 免费,开源,便于使用 22 | 23 | ## 使用场景 Where-to-use 24 | - My ISP only allows me to use one port | 我的网络提供商只允许我使用一个外网端口 25 | - I want to use intranet mapping services to map my port, but I can only map one port. | 我想映射内网端口,但我只能映射一个 26 | - I want to manage my server in only one port, and I want to encrypt all my activities, because there's lots of important data in my server | 我想只用一个端口访问我的服务器,并且我对安全的要求非常高,因为上面有许多我的重要数据 27 | - My company have only one outside network port, but there are ERP, OA in different ports even different servers | 我的公司只有一个外网端口,但 ERP, OA 等却在不同端口,甚至在不同的服务器上 28 | - I want to access my internal network, but I cannot use VPN or intranet mapping | 我希望访问我的内网,但我不能用虚拟专用网络和内网映射 29 | - I want to turn Socket to WebSocket without editing the program | 我想把 Socket 不经修改程序转为 WebSocket 30 | - I only have 80 port and I want to run OnePort and HttpServer together | 我只有 80 端口却想将 OnePort 与 Http 服务一起运行 31 | > ** OnePort 允许你以自由地以非商业化模式使用,商业(除集成至 CloudSky)使用请联系邮箱 xtl@xtlsoft.top ,以便我们提供更完善的服务。** 32 | 33 | ## 灵感来源 34 | 作者一台服务器,一开始 ISP 只开80端口,为了一起使用 Web, RDP, MySQL, NoSQL, SSH, WebSocket 等服务,费劲脑筋上网查找,发现找不到。虽然后来联系 ISP 关闭了 WAF,全端口映射,但是可能有的小伙伴还有这种问题,故开发了 OnePort。 35 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "06fdadd5597f6c2b2516f7e8dbc13999", 8 | "packages": [ 9 | { 10 | "name": "workerman/workerman", 11 | "version": "v3.5.4", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/walkor/Workerman.git", 15 | "reference": "f6e129079e5f5398bffc218024f2dd95678fc564" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://files.phpcomposer.com/files/walkor/Workerman/f6e129079e5f5398bffc218024f2dd95678fc564.zip", 20 | "reference": "f6e129079e5f5398bffc218024f2dd95678fc564", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3" 25 | }, 26 | "suggest": { 27 | "ext-event": "For better performance. " 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-4": { 32 | "Workerman\\": "./" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "walkor", 42 | "email": "walkor@workerman.net", 43 | "homepage": "http://www.workerman.net", 44 | "role": "Developer" 45 | } 46 | ], 47 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 48 | "homepage": "http://www.workerman.net", 49 | "keywords": [ 50 | "asynchronous", 51 | "event-loop" 52 | ], 53 | "time": "2017-12-06T04:17:52+00:00" 54 | } 55 | ], 56 | "packages-dev": [], 57 | "aliases": [], 58 | "minimum-stability": "stable", 59 | "stability-flags": [], 60 | "prefer-stable": false, 61 | "prefer-lowest": false, 62 | "platform": { 63 | "php": ">=5.4" 64 | }, 65 | "platform-dev": [] 66 | } 67 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | */ 14 | 15 | namespace CloudSky\OnePort; 16 | 17 | class Client { 18 | 19 | protected $server; 20 | protected $transport = "tcp"; 21 | protected $username = "guest"; 22 | protected $password = "guest"; 23 | protected $map = []; 24 | 25 | public function __construct($server){ 26 | 27 | $this->server = "ws://$server"; 28 | 29 | } 30 | 31 | public function enableSSL($bool = true){ 32 | 33 | if($bool) $this->transport = "ssl"; 34 | else $this->transport = "tcp"; 35 | 36 | return $this; 37 | 38 | } 39 | 40 | public function login($user, $pass){ 41 | 42 | $this->username = $user; 43 | $this->password = $pass; 44 | 45 | return $this; 46 | 47 | } 48 | 49 | public function map($orgin, $mirror, $trans = "tcp"){ 50 | 51 | // $atc = new \Workerman\Connection\AsyncTcpConnection($this->server); 52 | $server = $this->server; 53 | 54 | $fpkg = $this->username ."||" . $this->password . "||" . $orgin . "||" . $trans; 55 | 56 | $worker = new \Workerman\Worker($mirror); 57 | 58 | $worker->onConnect = function ($conn) use ($fpkg, $server) { 59 | $conn->atc = new \Workerman\Connection\AsyncTcpConnection($server); 60 | 61 | $conn->atc->onMessage = function($c, $m) use ($conn){ 62 | $conn->send(base64_decode($m)); 63 | }; 64 | $conn->atc->onClose = function($c) use ($conn){ 65 | $conn->close(); 66 | }; 67 | 68 | $conn->atc->connect(); 69 | $conn->atc->send($fpkg); 70 | }; 71 | 72 | $worker->onMessage = function($conn, $msg){ 73 | // if($atc !== "") 74 | $conn->atc->send(base64_encode($msg)); 75 | }; 76 | 77 | $worker->onClose = function($conn){ 78 | // if($atc !== "") 79 | $conn->atc->close(); 80 | }; 81 | 82 | $this->map[] = [$orgin, $mirror, $trans]; 83 | 84 | return $this; 85 | 86 | } 87 | 88 | public function importMap($map){ 89 | 90 | if(!is_array($map)){ 91 | $map = \json_decode(file_get_contents($map), 1); 92 | } 93 | 94 | foreach($map as $v) { 95 | 96 | $this->map($v[0], $v[1], $v[2]); 97 | 98 | } 99 | 100 | return $this; 101 | 102 | } 103 | 104 | public function exportMap($map){ 105 | 106 | \file_put_contents($map, \json_encode($this->map)); 107 | 108 | return $this; 109 | 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /Document-zh_cn.md: -------------------------------------------------------------------------------- 1 | # OnePort 文档 2 | 3 | ## 1. Server 4 | > 这里,您将学习OnePort服务器用法。 5 | 6 | ### 1.1 实例化一个Server 7 | ```php 8 | importConfig('path/to/config.json'); 22 | ``` 23 | 配置文件须是Json格式的。例子: 24 | 25 | ```json 26 | { 27 | "name": "abc", 28 | "count": "3", 29 | "http.type": "proxy", 30 | "http.param": "tcp:\/\/127.0.0.1:80", 31 | "user": { 32 | "test": { 33 | "password": "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", 34 | "whitelist": [], 35 | "blacklist": [], 36 | "shortcut": [] 37 | } 38 | }, 39 | "shortcut": [] 40 | } 41 | ``` 42 | 43 | #### 1.2.2 运行时导入配置 44 | 单条导入: 45 | ```php 46 | $server->config('key', 'value'); 47 | ``` 48 | 多条导入: 49 | ```php 50 | $server->config([ 51 | 'key1' => 'value1', 52 | 'key2' => 'value2', 53 | '...' => '...' 54 | ]); 55 | ``` 56 | #### 1.2.3 配置项详解 57 | | 名称 | 解释 | 例子 | 58 | | ---- | ---- | ---- | 59 | | name | 本Worker的名称,仅用来标识,可以为空。 | testOnePortWorker | 60 | | count | 启动进程数,一般为CPU线程数 | 3 | 61 | | http.type | Http访问时的handler | jump 或 proxy 或 handle | 62 | | http.param | Http访问时所使用Handler的参数 | jump例子:http://baidu.com/ proxy例子:tcp://127.0.0.1:80 handle例子:/home/oneport/handler.php | 63 | 64 | ### 1.3 配置用户 65 | #### 1.3.1 添加用户 66 | ```php 67 | $server->addUser('someUser', 'password'); 68 | ``` 69 | #### 1.3.2 删除用户 70 | ```php 71 | $server->removeUser('someUser'); 72 | ``` 73 | #### 1.3.3 修改用户密码 74 | ```php 75 | $server->setPassword('someUser', 'password'); 76 | ``` 77 | ### 1.4 黑白名单 78 | #### 1.4.1 为用户白名单中添加内容: 79 | ```php 80 | $server->addWhiteList('someUser', 'tcp://something.let.someuser.see:1234'); 81 | ``` 82 | #### 1.4.2 为用户黑名单中添加内容: 83 | ```php 84 | $server->addBlackList('someUser', 'tcp://something.don.t.let.someuser.see:1234'); 85 | ``` 86 | #### 1.4.3 为用户关闭白名单 87 | ```php 88 | $server->disableWhiteList('someUser'); 89 | ``` 90 | #### 1.4.4 为用户关闭黑名单 91 | ```php 92 | $server->disableBlackList('someUser'); 93 | ``` 94 | #### 1.4.5 一些说明 95 | 当黑白名单都开启时,优先使用白名单。 96 | 97 | ### 1.5 导出配置 98 | ```php 99 | $server->exportConfig('path/to/config.json'); 100 | ``` 101 | 102 | ### 1.6 监听某一端口 103 | ```php 104 | $server->listen('0.0.0.0:5280'); 105 | ``` 106 | 107 | ## 2. Client 108 | > 这里,您将学习 OnePort 客户端的使用。 109 | 110 | ### 2.1 实例化一个 Client 111 | ```php 112 | $client = new \CloudSky\OnePort\Client("127.0.0.1:5280"); 113 | ``` 114 | 参数说明:唯一的一个参数是 OnePort 服务端的地址。 115 | 116 | ### 2.2 使用与不适用SSL 117 | > 目前服务端原生还不支持SSL。 118 | > 默认将不适用SSL。 119 | 120 | ```php 121 | $client->enableSSL(false); 122 | ``` 123 | 有一个参数,为假则不启用,为真则启用。 124 | 125 | ### 2.3 登录服务器 126 | OnePort 服务端为了安全,需要用户登陆。 127 | 128 | ```php 129 | $client->login("someUser", "password"); 130 | ``` 131 | 132 | 第一个参数是用户名,第二个参数是密码。 133 | 134 | ### 2.4 导入一个Map 135 | Map 是远程地址与本地地址映射关系的描述文件,是可选的。 136 | 137 | 一个示例Map: 138 | ```json 139 | [ 140 | ["tcp:\/\/10.0.50.23:443", "tcp:\/\/127.0.0.23:443", "tcp"] 141 | ] 142 | ``` 143 | 第一个是源地址,第二个是映射到的本机地址,第三个是SSL(一般不需要开启,若设置为“SSL”,则会把SSL先转为普通TCP再传输)。 144 | 145 | ```php 146 | $client->importMap("path/to/map.json"); 147 | ``` 148 | 149 | ### 2.5 映射一个地址 150 | ```php 151 | //普通地址Map,把tcp://10.0.50.23:22映射到127.0.0.23:22 152 | $client->map("tcp://10.0.50.23:22", "tcp://127.0.0.23:22"); 153 | 154 | //Alias映射,将Alias “aliasTest” 映射到本地。 155 | $client->map("aliasTest", "tcp://127.0.0.21:2333"); 156 | ``` 157 | 158 | ### 2.6 导出Map 159 | ```php 160 | $client->exportMap("path/to/map.json"); 161 | ``` 162 | 163 | ## 3. 运行 164 | 无论是什么,都记得执行这一句: 165 | ```php 166 | \Workerman\Worker::runAll(); 167 | ``` 168 | 169 | ## 4. 一个用于开发的EchoServer 170 | 171 | ### 4.1 基本用法 172 | 很简单,一句话启动: 173 | ```php 174 | $echoserver = new \CloudSky\OnePort\Dev\EchoServer("tcp://127.0.0.1:3245"); 175 | ``` 176 | 唯一的一个参数是监听地址,记得也要使用 3 中的方法启动。 177 | 178 | ### 4.2 自定义Worker 179 | 很简单: 180 | ```php 181 | $worker = new \Workerman\Worker("tcp://127.0.0.1:3246"); 182 | $echoserver->serve($worker); 183 | ``` 184 | -------------------------------------------------------------------------------- /src/Server.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | */ 14 | 15 | namespace CloudSky\OnePort; 16 | 17 | class Server { 18 | 19 | protected $config; 20 | protected $id = 0; 21 | protected $firstMessage = []; 22 | protected $atc = []; 23 | 24 | public function __construct($conf = []){ 25 | 26 | if(is_array($conf)){ 27 | return $this->config($conf); 28 | }else{ 29 | return $this->importConfig($conf); 30 | } 31 | 32 | } 33 | 34 | public function config($n, $v = ""){ 35 | 36 | if(is_array($n)){ 37 | foreach($n as $k=>$va){ 38 | $this->config($k, $va); 39 | } 40 | }else{ 41 | $this->config[$n] = $v; 42 | } 43 | 44 | return $this; 45 | 46 | } 47 | 48 | public function importConfig($file){ 49 | 50 | 51 | $this->config( 52 | json_decode( 53 | file_get_contents($file), 54 | 1 55 | ) 56 | ); 57 | 58 | return $this; 59 | 60 | } 61 | 62 | public function exportConfig($file = ""){ 63 | 64 | $data = json_encode($this->config, \JSON_PRETTY_PRINT); 65 | 66 | if($file){ 67 | 68 | file_put_contents($file, $data); 69 | 70 | return $this; 71 | 72 | }else{ 73 | 74 | return $data; 75 | 76 | } 77 | 78 | } 79 | 80 | public function listen($addr){ 81 | 82 | $this->worker = new \Workerman\Worker("websockethack://$addr"); 83 | 84 | $this->worker->name = $this->config['name']; 85 | $this->worker->count = $this->config['count']; 86 | 87 | $this->worker->onHttpRequest = [$this, 'handleHttp']; 88 | $this->worker->onMessage = [$this, 'handleMessageGateway']; 89 | $this->worker->onConnect = [$this, 'handleConnect']; 90 | $this->worker->onClose = [$this, 'handleClose']; 91 | 92 | return $this; 93 | 94 | } 95 | 96 | public function getCount(){ 97 | return $this->config['count']; 98 | } 99 | 100 | public function handleConnect($conn){ 101 | 102 | $conn->id = ++$this->id; 103 | $this->firstMessage[$conn->id] = true; 104 | 105 | } 106 | 107 | public function handleClose($conn){ 108 | 109 | if(isset($this->atc[$conn->id])) 110 | $this->atc[$conn->id]->close(); 111 | 112 | } 113 | 114 | public function handleMessageGateway($conn, $msg){ 115 | 116 | if($this->firstMessage[$conn->id]){ 117 | $this->firstMessage[$conn->id] = false; 118 | $this->handleFirstMessage($conn, $msg); 119 | }else{ 120 | $this->handleMessage($conn, $msg); 121 | } 122 | 123 | } 124 | 125 | public function handleMessage($conn, $msg){ 126 | 127 | $this->atc[$conn->id]->send(base64_decode($msg)); 128 | 129 | } 130 | 131 | public function handleFirstMessage($conn, $msg){ 132 | 133 | $msg = explode("||", $msg); 134 | $user = $msg[0]; 135 | $pass = $msg[1]; 136 | $uri = $msg[2]; 137 | $trans = $msg[3]; 138 | 139 | if(!$this->auth($user, $pass)){ 140 | $conn->send("__MCON__:Auth Error"); 141 | $conn->close(); 142 | return; 143 | } 144 | 145 | $uri = $this->dealUri($uri, $user); 146 | if($uri == false){ 147 | $conn->send("__MCON__:Permission Denied"); 148 | $conn->close(); 149 | return; 150 | } 151 | 152 | $id = $conn->id; 153 | 154 | $atc = new \Workerman\Connection\AsyncTcpConnection($uri); 155 | $atc->transport = $trans; 156 | 157 | $atc->onMessage = function($con, $m) use ($conn){ 158 | $conn->send(base64_encode($m)); 159 | }; 160 | $atc->onClose = function($con) use ($conn){ 161 | $conn->close(); 162 | }; 163 | 164 | $atc->connect(); 165 | 166 | $this->atc[$id] = $atc; 167 | 168 | } 169 | 170 | public function addUser($name, $password){ 171 | 172 | @$this->config['user'][$name] = [ 173 | "password" => hash('sha512', $password), 174 | "whitelist" => [], 175 | "blacklist" => [], 176 | "shortcut" => [] 177 | ]; 178 | 179 | return $this; 180 | 181 | } 182 | 183 | public function removeUser($name){ 184 | 185 | unset($this->config['user'][$name]); 186 | 187 | return $this; 188 | 189 | } 190 | 191 | public function setPassword($name, $password){ 192 | 193 | $this->config['user'][$name]['password'] = hash('sha512', $password); 194 | 195 | return $this; 196 | 197 | } 198 | 199 | public function addWhiteList($user, $addr){ 200 | 201 | if(is_array($addr)){ 202 | 203 | foreach($addr as $a){ 204 | $this->addWhiteList($user, $a); 205 | } 206 | 207 | }else{ 208 | 209 | $this->config['user'][$user]['whitelist'][] = $addr; 210 | 211 | } 212 | 213 | return $this; 214 | 215 | } 216 | 217 | public function addBlackList($user, $addr){ 218 | 219 | if(is_array($addr)){ 220 | 221 | foreach($addr as $a){ 222 | $this->addBlackList($user, $a); 223 | } 224 | 225 | }else{ 226 | 227 | $this->config['user'][$user]['blacklist'][] = $addr; 228 | 229 | } 230 | 231 | return $this; 232 | 233 | } 234 | 235 | public function disableWhiteList($user){ 236 | 237 | $this->config['user'][$user]['whitelist'] = []; 238 | 239 | return $this; 240 | 241 | } 242 | 243 | public function disableBlackList($user){ 244 | 245 | $this->config['user'][$user]['blacklist'] = []; 246 | 247 | return $this; 248 | 249 | } 250 | 251 | public function addUserShortcut($name, $alias, $addr){ 252 | 253 | $this->config['user'][$name]['shortcut'][$alias] = $addr; 254 | 255 | return $this; 256 | 257 | } 258 | 259 | public function removeUserShortcut($name, $alias){ 260 | 261 | unset($this->config['user'][$name]['shortcut'][$alias]); 262 | 263 | return $this; 264 | 265 | } 266 | 267 | public function addShortcut($alias, $addr, $name = null){ 268 | 269 | if($name !== null){ 270 | return $this->addUserShortcut($name, $alias, $addr); 271 | } 272 | 273 | $this->config['shortcut'][$alias] = $addr; 274 | 275 | return $this; 276 | 277 | } 278 | 279 | public function removeShortcut($alias, $user = null){ 280 | 281 | if($name !== null){ 282 | return $this->removeUserShortcut($user, $alias); 283 | } 284 | 285 | unset($this->config['shortcut'][$alias]); 286 | 287 | return $this; 288 | 289 | } 290 | 291 | protected function auth($name, $pass){ 292 | 293 | $realpass = $this->config['user'][$name]['password']; 294 | $inputpass = hash('sha512', $pass); 295 | 296 | return ( $inputpass === $realpass ); 297 | 298 | } 299 | 300 | protected function dealUri($uri, $user){ 301 | 302 | $wl = $this->config['user'][$user]['whitelist']; 303 | $bl = $this->config['user'][$user]['blacklist']; 304 | $al = @array_merge($this->config['shortcut'], $this->config['user'][$user]['shortcut']); 305 | 306 | $ok = false; 307 | 308 | if($wl != []){ 309 | $ok = in_array($uri, $wl); 310 | }else{ 311 | $ok = !(in_array($uri, $bl)); 312 | } 313 | 314 | if(!$ok){ 315 | return false; 316 | } 317 | 318 | if(isset($al[$uri])){ 319 | $uri = $al[$uri]; 320 | } 321 | 322 | return $uri; 323 | 324 | } 325 | 326 | public function handleHttp($conn, $msg){ 327 | 328 | $type = $this->config['http.type']; 329 | $param = $this->config['http.param']; 330 | switch($type){ 331 | 332 | case 'jump': 333 | $conn->send("HTTP/1.1 302 Found\r\nServer: OnePort\r\nlocation: $param\r\n\r\n\r\n", 1); 334 | $conn->close(); 335 | break; 336 | 337 | case 'handle': 338 | $conn->send("HTTP/1.1 200 OK\r\nServer: OnePort\r\n" . shell_exec("php-cgi $param"), 1); 339 | $conn->close(); 340 | break; 341 | 342 | case 'proxy': 343 | $atc = new \Workerman\Connection\AsyncTcpConnection($param); 344 | $atc->onMessage = function($c, $m) use ($conn){ 345 | $conn->send($m, 1); 346 | }; 347 | $atc->onClose = function($c) use ($conn){ 348 | $conn->close(); 349 | }; 350 | 351 | $atc->connect(); 352 | $atc->send($msg); 353 | } 354 | 355 | } 356 | 357 | } -------------------------------------------------------------------------------- /src/Protocols/Websockethack.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright walkor 11 | * @link http://www.workerman.net/ 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | * @editby xtl 14 | */ 15 | namespace Workerman\Protocols; 16 | 17 | use Workerman\Connection\ConnectionInterface; 18 | use Workerman\Connection\TcpConnection; 19 | use Workerman\Worker; 20 | 21 | /** 22 | * WebSocket protocol. 23 | */ 24 | class Websockethack implements \Workerman\Protocols\ProtocolInterface 25 | { 26 | /** 27 | * Websocket blob type. 28 | * 29 | * @var string 30 | */ 31 | const BINARY_TYPE_BLOB = "\x81"; 32 | 33 | /** 34 | * Websocket arraybuffer type. 35 | * 36 | * @var string 37 | */ 38 | const BINARY_TYPE_ARRAYBUFFER = "\x82"; 39 | 40 | /** 41 | * Check the integrity of the package. 42 | * 43 | * @param string $buffer 44 | * @param ConnectionInterface $connection 45 | * @return int 46 | */ 47 | public static function input($buffer, ConnectionInterface $connection) 48 | { 49 | // Receive length. 50 | $recv_len = strlen($buffer); 51 | // We need more data. 52 | if ($recv_len < 2) { 53 | return 0; 54 | } 55 | 56 | // Has not yet completed the handshake. 57 | if (empty($connection->websocketHandshake)) { 58 | return static::dealHandshake($buffer, $connection); 59 | } 60 | 61 | // Buffer websocket frame data. 62 | if ($connection->websocketCurrentFrameLength) { 63 | // We need more frame data. 64 | if ($connection->websocketCurrentFrameLength > $recv_len) { 65 | // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. 66 | return 0; 67 | } 68 | } else { 69 | $firstbyte = ord($buffer[0]); 70 | $secondbyte = ord($buffer[1]); 71 | $data_len = $secondbyte & 127; 72 | $is_fin_frame = $firstbyte >> 7; 73 | $masked = $secondbyte >> 7; 74 | $opcode = $firstbyte & 0xf; 75 | switch ($opcode) { 76 | case 0x0: 77 | break; 78 | // Blob type. 79 | case 0x1: 80 | break; 81 | // Arraybuffer type. 82 | case 0x2: 83 | break; 84 | // Close package. 85 | case 0x8: 86 | // Try to emit onWebSocketClose callback. 87 | if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { 88 | try { 89 | call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); 90 | } catch (\Exception $e) { 91 | Worker::log($e); 92 | exit(250); 93 | } catch (\Error $e) { 94 | Worker::log($e); 95 | exit(250); 96 | } 97 | } // Close connection. 98 | else { 99 | $connection->close(); 100 | } 101 | return 0; 102 | // Ping package. 103 | case 0x9: 104 | // Try to emit onWebSocketPing callback. 105 | if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { 106 | try { 107 | call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection); 108 | } catch (\Exception $e) { 109 | Worker::log($e); 110 | exit(250); 111 | } catch (\Error $e) { 112 | Worker::log($e); 113 | exit(250); 114 | } 115 | } // Send pong package to client. 116 | else { 117 | $connection->send(pack('H*', '8a00'), true); 118 | } 119 | 120 | // Consume data from receive buffer. 121 | if (!$data_len) { 122 | $head_len = $masked ? 6 : 2; 123 | $connection->consumeRecvBuffer($head_len); 124 | if ($recv_len > $head_len) { 125 | return static::input(substr($buffer, $head_len), $connection); 126 | } 127 | return 0; 128 | } 129 | break; 130 | // Pong package. 131 | case 0xa: 132 | // Try to emit onWebSocketPong callback. 133 | if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { 134 | try { 135 | call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection); 136 | } catch (\Exception $e) { 137 | Worker::log($e); 138 | exit(250); 139 | } catch (\Error $e) { 140 | Worker::log($e); 141 | exit(250); 142 | } 143 | } 144 | // Consume data from receive buffer. 145 | if (!$data_len) { 146 | $head_len = $masked ? 6 : 2; 147 | $connection->consumeRecvBuffer($head_len); 148 | if ($recv_len > $head_len) { 149 | return static::input(substr($buffer, $head_len), $connection); 150 | } 151 | return 0; 152 | } 153 | break; 154 | // Wrong opcode. 155 | default : 156 | echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; 157 | $connection->close(); 158 | return 0; 159 | } 160 | 161 | // Calculate packet length. 162 | $head_len = 6; 163 | if ($data_len === 126) { 164 | $head_len = 8; 165 | if ($head_len > $recv_len) { 166 | return 0; 167 | } 168 | $pack = unpack('nn/ntotal_len', $buffer); 169 | $data_len = $pack['total_len']; 170 | } else { 171 | if ($data_len === 127) { 172 | $head_len = 14; 173 | if ($head_len > $recv_len) { 174 | return 0; 175 | } 176 | $arr = unpack('n/N2c', $buffer); 177 | $data_len = $arr['c1']*4294967296 + $arr['c2']; 178 | } 179 | } 180 | $current_frame_length = $head_len + $data_len; 181 | 182 | $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; 183 | if ($total_package_size > TcpConnection::$maxPackageSize) { 184 | echo "error package. package_length=$total_package_size\n"; 185 | $connection->close(); 186 | return 0; 187 | } 188 | 189 | if ($is_fin_frame) { 190 | return $current_frame_length; 191 | } else { 192 | $connection->websocketCurrentFrameLength = $current_frame_length; 193 | } 194 | } 195 | 196 | // Received just a frame length data. 197 | if ($connection->websocketCurrentFrameLength === $recv_len) { 198 | static::decode($buffer, $connection); 199 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 200 | $connection->websocketCurrentFrameLength = 0; 201 | return 0; 202 | } // The length of the received data is greater than the length of a frame. 203 | elseif ($connection->websocketCurrentFrameLength < $recv_len) { 204 | static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); 205 | $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); 206 | $current_frame_length = $connection->websocketCurrentFrameLength; 207 | $connection->websocketCurrentFrameLength = 0; 208 | // Continue to read next frame. 209 | return static::input(substr($buffer, $current_frame_length), $connection); 210 | } // The length of the received data is less than the length of a frame. 211 | else { 212 | return 0; 213 | } 214 | } 215 | 216 | /** 217 | * Websocket encode. 218 | * 219 | * @param string $buffer 220 | * @param ConnectionInterface $connection 221 | * @return string 222 | */ 223 | public static function encode($buffer, ConnectionInterface $connection) 224 | { 225 | if (!is_scalar($buffer)) { 226 | throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); 227 | } 228 | $len = strlen($buffer); 229 | if (empty($connection->websocketType)) { 230 | $connection->websocketType = static::BINARY_TYPE_BLOB; 231 | } 232 | 233 | $first_byte = $connection->websocketType; 234 | 235 | if ($len <= 125) { 236 | $encode_buffer = $first_byte . chr($len) . $buffer; 237 | } else { 238 | if ($len <= 65535) { 239 | $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; 240 | } else { 241 | $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; 242 | } 243 | } 244 | 245 | // Handshake not completed so temporary buffer websocket data waiting for send. 246 | if (empty($connection->websocketHandshake)) { 247 | if (empty($connection->tmpWebsocketData)) { 248 | $connection->tmpWebsocketData = ''; 249 | } 250 | // If buffer has already full then discard the current package. 251 | if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { 252 | if ($connection->onError) { 253 | try { 254 | call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); 255 | } catch (\Exception $e) { 256 | Worker::log($e); 257 | exit(250); 258 | } catch (\Error $e) { 259 | Worker::log($e); 260 | exit(250); 261 | } 262 | } 263 | return ''; 264 | } 265 | $connection->tmpWebsocketData .= $encode_buffer; 266 | // Check buffer is full. 267 | if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { 268 | if ($connection->onBufferFull) { 269 | try { 270 | call_user_func($connection->onBufferFull, $connection); 271 | } catch (\Exception $e) { 272 | Worker::log($e); 273 | exit(250); 274 | } catch (\Error $e) { 275 | Worker::log($e); 276 | exit(250); 277 | } 278 | } 279 | } 280 | 281 | // Return empty string. 282 | return ''; 283 | } 284 | 285 | return $encode_buffer; 286 | } 287 | 288 | /** 289 | * Websocket decode. 290 | * 291 | * @param string $buffer 292 | * @param ConnectionInterface $connection 293 | * @return string 294 | */ 295 | public static function decode($buffer, ConnectionInterface $connection) 296 | { 297 | $masks = $data = $decoded = null; 298 | $len = ord($buffer[1]) & 127; 299 | if ($len === 126) { 300 | $masks = substr($buffer, 4, 4); 301 | $data = substr($buffer, 8); 302 | } else { 303 | if ($len === 127) { 304 | $masks = substr($buffer, 10, 4); 305 | $data = substr($buffer, 14); 306 | } else { 307 | $masks = substr($buffer, 2, 4); 308 | $data = substr($buffer, 6); 309 | } 310 | } 311 | for ($index = 0; $index < strlen($data); $index++) { 312 | $decoded .= $data[$index] ^ $masks[$index % 4]; 313 | } 314 | if ($connection->websocketCurrentFrameLength) { 315 | $connection->websocketDataBuffer .= $decoded; 316 | return $connection->websocketDataBuffer; 317 | } else { 318 | if ($connection->websocketDataBuffer !== '') { 319 | $decoded = $connection->websocketDataBuffer . $decoded; 320 | $connection->websocketDataBuffer = ''; 321 | } 322 | return $decoded; 323 | } 324 | } 325 | 326 | /** 327 | * Websocket handshake. 328 | * 329 | * @param string $buffer 330 | * @param \Workerman\Connection\TcpConnection $connection 331 | * @return int 332 | */ 333 | protected static function dealHandshake($buffer, $connection) 334 | { 335 | // HTTP protocol. 336 | if (0 === strpos($buffer, 'GET')) { 337 | // Find \r\n\r\n. 338 | $heder_end_pos = strpos($buffer, "\r\n\r\n"); 339 | if (!$heder_end_pos) { 340 | return 0; 341 | } 342 | $header_length = $heder_end_pos + 4; 343 | 344 | // Get Sec-WebSocket-Key. 345 | $Sec_WebSocket_Key = ''; 346 | if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { 347 | $Sec_WebSocket_Key = $match[1]; 348 | } else { 349 | // $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.workerman.net/Error1 for detail.", 350 | // true); 351 | // $connection->close(); 352 | call_user_func($connection->worker->onHttpRequest, $connection, $buffer); 353 | return 0; 354 | } 355 | // Calculation websocket key. 356 | $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 357 | // Handshake response data. 358 | $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; 359 | $handshake_message .= "Upgrade: websocket\r\n"; 360 | $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; 361 | $handshake_message .= "Connection: Upgrade\r\n"; 362 | $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; 363 | $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; 364 | // Mark handshake complete.. 365 | $connection->websocketHandshake = true; 366 | // Websocket data buffer. 367 | $connection->websocketDataBuffer = ''; 368 | // Current websocket frame length. 369 | $connection->websocketCurrentFrameLength = 0; 370 | // Current websocket frame data. 371 | $connection->websocketCurrentFrameBuffer = ''; 372 | // Consume handshake data. 373 | $connection->consumeRecvBuffer($header_length); 374 | // Send handshake response. 375 | $connection->send($handshake_message, true); 376 | 377 | // There are data waiting to be sent. 378 | if (!empty($connection->tmpWebsocketData)) { 379 | $connection->send($connection->tmpWebsocketData, true); 380 | $connection->tmpWebsocketData = ''; 381 | } 382 | // blob or arraybuffer 383 | if (empty($connection->websocketType)) { 384 | $connection->websocketType = static::BINARY_TYPE_BLOB; 385 | } 386 | // Try to emit onWebSocketConnect callback. 387 | if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { 388 | static::parseHttpHeader($buffer); 389 | try { 390 | call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); 391 | } catch (\Exception $e) { 392 | Worker::log($e); 393 | exit(250); 394 | } catch (\Error $e) { 395 | Worker::log($e); 396 | exit(250); 397 | } 398 | if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { 399 | $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); 400 | } 401 | $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); 402 | } 403 | if (strlen($buffer) > $header_length) { 404 | return static::input(substr($buffer, $header_length), $connection); 405 | } 406 | return 0; 407 | } // Is flash policy-file-request. 408 | elseif (0 === strpos($buffer, 'send($policy_xml, true); 411 | $connection->consumeRecvBuffer(strlen($buffer)); 412 | return 0; 413 | } 414 | // Bad websocket handshake request. 415 | $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.workerman.net/Error1 for detail.", 416 | true); 417 | $connection->close(); 418 | return 0; 419 | } 420 | 421 | /** 422 | * Parse http header. 423 | * 424 | * @param string $buffer 425 | * @return void 426 | */ 427 | protected static function parseHttpHeader($buffer) 428 | { 429 | // Parse headers. 430 | list($http_header, ) = explode("\r\n\r\n", $buffer, 2); 431 | $header_data = explode("\r\n", $http_header); 432 | 433 | if ($_SERVER) { 434 | $_SERVER = array(); 435 | } 436 | 437 | list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', 438 | $header_data[0]); 439 | 440 | unset($header_data[0]); 441 | foreach ($header_data as $content) { 442 | // \r\n\r\n 443 | if (empty($content)) { 444 | continue; 445 | } 446 | list($key, $value) = explode(':', $content, 2); 447 | $key = str_replace('-', '_', strtoupper($key)); 448 | $value = trim($value); 449 | $_SERVER['HTTP_' . $key] = $value; 450 | switch ($key) { 451 | // HTTP_HOST 452 | case 'HOST': 453 | $tmp = explode(':', $value); 454 | $_SERVER['SERVER_NAME'] = $tmp[0]; 455 | if (isset($tmp[1])) { 456 | $_SERVER['SERVER_PORT'] = $tmp[1]; 457 | } 458 | break; 459 | // cookie 460 | case 'COOKIE': 461 | parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); 462 | break; 463 | } 464 | } 465 | 466 | // QUERY_STRING 467 | $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); 468 | if ($_SERVER['QUERY_STRING']) { 469 | // $GET 470 | parse_str($_SERVER['QUERY_STRING'], $_GET); 471 | } else { 472 | $_SERVER['QUERY_STRING'] = ''; 473 | } 474 | } 475 | } 476 | --------------------------------------------------------------------------------