├── README.md ├── fun.php ├── ngrok.php └── ngrokd.php /README.md: -------------------------------------------------------------------------------- 1 | # ngrokd-php 2 | 3 | 一个简单的ngrokd服务器,使用php写的,需要swoole扩展才行,而且编译swoole扩展的时候必须,加上openssl参数,不然没法使用,可以用来看ngrokd的原理。不推荐部署,性能堪忧,正在完善中。 4 | 5 | 6 | 7 | 目前的问题 8 | 9 | 1. 不支持TCP映射,只支持http or https 10 | 2. 目前只能开一个进程,否则变量无法同步,导致不可预料的问题。 11 | 3. 大网页很慢。。。 12 | 4. 代码有些乱第一个swoole项目。 13 | 5. 不支持验证用户。 14 | 6. 不支持Hostname全域名参数。 15 | 16 | 17 | 18 | 至于怎么运行嘛,安装好php环境,跟swoole扩展,直接php ngrokd.php就可以了。。。,记得,修改$baseurl改成你泛域名噢,还有$sslinfo改成你的ssl证书路径。 19 | -------------------------------------------------------------------------------- /fun.php: -------------------------------------------------------------------------------- 1 | '2', 6 | 'MmVersion' => '1.7', 7 | 'ClientId' => MD5(time()), 8 | 'Error' => '', 9 | ); 10 | $json = array( 11 | 'Type' => 'AuthResp', 12 | 'Payload' => $Payload 13 | ); 14 | return json_encode($json); 15 | } 16 | 17 | function Pong($js) { 18 | $Payload = (object) array(); 19 | $json = array( 20 | 'Type' => 'Pong', 21 | 'Payload' => $Payload, 22 | ); 23 | return json_encode($json); 24 | } 25 | 26 | function ReqProxy($js) { 27 | $Payload = (object) array(); 28 | $json = array( 29 | 'Type' => 'ReqProxy', 30 | 'Payload' => $Payload, 31 | ); 32 | return json_encode($json); 33 | } 34 | 35 | function ReqTunnel($fd, $js) { 36 | /* 37 | json:{"Type":"NewTunnel","Payload":{"ReqId":"47fa0e022","Url":"https://jobtest.t 38 | unnel.mobi","Protocol":"https","Error":""}} 39 | */ 40 | 41 | global $baseurl; 42 | 43 | if ($js['Payload']['Protocol'] == 'http' || $js['Payload']['Protocol'] == 'https') { 44 | if (strlen($js['Payload']['Subdomain']) > 0) { 45 | $url_ = $js['Payload']['Subdomain']; 46 | } else { 47 | $url_ = substr(MD5(time() . rand(0, 1000)), 0, 8); 48 | } 49 | $url = $url_ . '.' . $baseurl; 50 | $tunnelist[$url_] = $fd; 51 | } 52 | 53 | 54 | 55 | 56 | $Payload = array('ReqId' => $js['Payload']['ReqId'], 57 | 'Protocol' => $js['Payload']['Protocol'], 58 | 'Error' => '', 59 | 'Url' => $url, 60 | ); 61 | $json = array( 62 | 'Type' => 'NewTunnel', 63 | 'Payload' => $Payload, 64 | ); 65 | return array('json' => json_encode($json), 66 | 'tunnelist' => $tunnelist, 67 | ); 68 | } 69 | 70 | function httphead($request) { 71 | $http = explode("\n", $request); 72 | $REQUEST_METHOD = substr($http[0], 0, strpos($http[0], ' ')); 73 | $back = array(); 74 | foreach ($http as $k => $z) { 75 | if ($k > 0) { 76 | $key = trim(substr($z, 0, strpos($z, ':'))); 77 | $value = trim(substr($z, strpos($z, ':') + 1)); 78 | $back[$key] = $value; 79 | } 80 | } 81 | $back['REQUEST_METHOD'] = $REQUEST_METHOD; 82 | return $back; 83 | } 84 | 85 | function RegProxy($fdinfo, $reglist, $fd, $js) { 86 | 87 | /* 88 | {"Type":"StartProxy","Payload":{"Url":"http://jobtest1.tunnel.mobi","Cl 89 | ientAddr":"183.48.73.230:33604"}} 90 | */ 91 | global $baseurl; 92 | $xx = $reglist[0]; 93 | 94 | $Payload = array('Url' => $xx['Protocol'] . '://' . $xx['Subdomain'] . '.' . $baseurl, 95 | 'ClientAddr' => $fdinfo['remote_ip'] . ':' . $fdinfo['remote_port'], 96 | ); 97 | $json = array( 98 | 'Type' => 'StartProxy', 99 | 'Payload' => $Payload, 100 | ); 101 | return json_encode($json); 102 | } 103 | 104 | /* 105 | 网络字节序 106 | */ 107 | 108 | function tolen($v) { 109 | list ($hi, $lo) = array_values(unpack("N*N*", $v)); 110 | if ($hi < 0) 111 | $hi += (1 << 32); 112 | if ($ho < 0) 113 | $lo += (1 << 32); 114 | return ($hi << 32) + $lo; 115 | } 116 | 117 | /* 机器字节序 */ 118 | 119 | function tolen1($v) { 120 | list ($hi, $lo) = array_values(unpack("L*L*", $v)); 121 | if ($hi < 0) 122 | $hi += (1 << 32); 123 | if ($ho < 0) 124 | $lo += (1 << 32); 125 | return ($hi << 32) + $lo; 126 | } 127 | 128 | /* 网络字节序 */ 129 | 130 | function lentobyte($len) { 131 | $xx = pack("N", $len); 132 | $xx1 = pack("C4", 0, 0, 0, 0); 133 | return $xx1 . $xx; 134 | } 135 | 136 | /* 机器字节序 */ 137 | 138 | function lentobyte1($len) { 139 | $xx = pack("L", $len); 140 | $xx1 = pack("C4", 0, 0, 0, 0); 141 | return $xx . $xx1; 142 | } 143 | 144 | ?> -------------------------------------------------------------------------------- /ngrok.php: -------------------------------------------------------------------------------- 1 | sockinfolist[$fd]['type']) && $serv->sockinfolist[$fd]['type'] == 1) { 7 | 8 | } 9 | //if proxy sock close http connect 10 | if (isset($serv->sockinfolist[$fd]['type']) && $serv->sockinfolist[$fd]['type'] == 2) { 11 | $serv->close($serv->sockinfolist[$fd]['tofd']); 12 | } 13 | echo "Client: Close.\n"; 14 | } 15 | 16 | static function connect($serv, $fd) { 17 | echo "Client:Connect.\n"; 18 | } 19 | 20 | static function receive($serv, $fd, $from_id, $data) { 21 | $fdinfo = $serv->connection_info($fd); 22 | if ($fdinfo['server_port'] == 9503 || $fdinfo['server_port'] == 9502) { 23 | 24 | if (!isset($serv->sockinfolist[$fd])) { 25 | $serv->sockinfolist[$fd] = array('type' => 1, //1 is contrl 26 | 'fd' => $fd, 27 | 'recvbuffer' => '', 28 | ); 29 | } 30 | 31 | if (strlen($data) > 0) { 32 | $serv->sockinfolist[$fd]['recvbuffer'] = $serv->sockinfolist[$fd]['recvbuffer'] . $data; 33 | } 34 | 35 | $recvbut = $serv->sockinfolist[$fd]['recvbuffer']; 36 | 37 | if ($serv->sockinfolist[$fd]['type'] == 1) { 38 | //get len; 39 | $lenbuf = substr($recvbut, 0, 8); 40 | $len = tolen1($lenbuf); 41 | if (strlen($recvbut) >= (8 + $len)) { 42 | $json = substr($recvbut, 8, $len); 43 | $js = json_decode($json, true); 44 | $send = ''; 45 | switch ($js['Type']) { 46 | case 'Auth': 47 | $send = Auth($js); 48 | break; 49 | case 'Ping': 50 | $send = Pong($js); 51 | // $send1=ReqProxy($js); 52 | break; 53 | case 'ReqTunnel': 54 | $back = ReqTunnel($fd, $js); 55 | $serv->reqproxylist = $back['tunnelist']; 56 | $send = $back['json']; 57 | break; 58 | //connect RegProxy 59 | case 'RegProxy': 60 | $tempsend = RegProxy($fdinfo, $serv->reglist, $fd, $js); 61 | if (strlen($tempsend) > 0) { 62 | $sendlen = lentobyte1(strlen($tempsend)); 63 | $serv->send($fd, $sendlen . $tempsend); 64 | 65 | if (count($serv->reglist)) { 66 | $xx = array_pop($serv->reglist); 67 | //send local 68 | if (strlen($xx['recvbut']) > 0) { 69 | $serv->send($fd, $xx['recvbut']); 70 | } 71 | 72 | $serv->sockinfolist[$fd] = array( 73 | 'type' => 2, //1 is contrl 74 | 'fd' => $fd, 75 | 'tofd' => $xx['fd'], 76 | 'recvbuffer' => $serv->sockinfolist[$fd]['recvbuffer'], 77 | ); 78 | 79 | // 80 | $serv->sockinfolist[$xx['fd']] = array('type' => 4, 81 | 'fd' => $xx['fd'], 82 | 'tofd' => $fd, 83 | 'recvbuffer' => $serv->sockinfolist[$xx['fd']]['recvbuffer'], 84 | ); 85 | } 86 | //关闭连接 87 | else { 88 | $serv->close($fd); 89 | } 90 | } 91 | break; 92 | } 93 | //send 94 | if (strlen($send) > 0) { 95 | $sendlen = lentobyte1(strlen($send)); 96 | $serv->send($fd, $sendlen . $send); 97 | } 98 | 99 | 100 | //edit buffer 101 | if (strlen($recvbut) == (8 + $len)) { 102 | $serv->sockinfolist[$fd]['recvbuffer'] = ''; 103 | } else { 104 | $serv->sockinfolist[$fd]['recvbuffer'] = substr($recvbut, 8 + $len); 105 | } 106 | } 107 | } 108 | //已经进入代理模式,数据直接转发给远程的socket就行了。。 109 | else { 110 | $serv->send($serv->sockinfolist[$fd]['tofd'], $recvbut); 111 | $serv->sockinfolist[$fd]['recvbuffer'] = ''; 112 | } 113 | } 114 | 115 | //81 http 448 https 116 | if ($fdinfo['server_port'] == 81 || $fdinfo['server_port'] == 448) { 117 | 118 | if (!isset($serv->sockinfolist[$fd])) { 119 | $serv->sockinfolist[$fd] = array('type' => 3, //1 is sock ,2 is proxy sock,3 is user sock,4 is connect proxy 120 | 'fd' => $fd, 121 | 'recvbuffer' => '', 122 | ); 123 | } 124 | 125 | 126 | if (strlen($data) > 0) { 127 | $serv->sockinfolist[$fd]['recvbuffer'] = $serv->sockinfolist[$fd]['recvbuffer'] . $data; 128 | } 129 | $recvbut = $serv->sockinfolist[$fd]['recvbuffer']; 130 | 131 | 132 | if ($serv->sockinfolist[$fd]['type'] == 3) { 133 | $httpinfo = httphead($recvbut); 134 | $Subdomain = substr($httpinfo['Host'], 0, strpos($httpinfo['Host'], '.')); 135 | $cfd = $serv->reqproxylist[$Subdomain]; 136 | 137 | if ($cfd > 0) { 138 | $Protocol = $fdinfo['server_port'] == 81 ? 'http' : 'https'; 139 | array_push($serv->reglist, array('Protocol' => $Protocol, 140 | 'Subdomain' => $Subdomain, 141 | 'recvbut' => $recvbut, 142 | 'fd' => $fd, 143 | )); 144 | $sendbuf = ReqProxy(null); 145 | $sendlen = lentobyte1(strlen($sendbuf)); 146 | $serv->send($cfd, $sendlen . $sendbuf); 147 | } else { 148 | $body = 'Tunnel ' . $httpinfo['Host'] . ' not found.'; 149 | $request = 'HTTP/1.0 404 Not Found.' . "\r\n" . 'Content-Length: ' . strlen($body) . "\r\n" . $body; 150 | $serv->send($fd, $request); 151 | $serv->close($fd); 152 | } 153 | } else { 154 | $serv->send($serv->sockinfolist[$fd]['tofd'], $recvbut); 155 | $serv->sockinfolist[$fd]['recvbuffer'] = ''; 156 | } 157 | } 158 | } 159 | 160 | } 161 | 162 | ?> -------------------------------------------------------------------------------- /ngrokd.php: -------------------------------------------------------------------------------- 1 | '/home/ssl/server.crt', 8 | 'ssl_key_file' => '/home/ssl/domain.key', 9 | ); 10 | 11 | $serv = new swoole_server("0.0.0.0", 9503, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); 12 | $serv->addlistener('0.0.0.0', 9502, SWOOLE_SOCK_TCP); //test 13 | $serv->addlistener('0.0.0.0', 81, SWOOLE_SOCK_TCP); //http 14 | $serv->addlistener('0.0.0.0', 448, SWOOLE_SOCK_TCP | SWOOLE_SSL); //https 15 | //var_dump($serv->connections); 16 | 17 | $serv->set(array( 18 | 'worker_num' => 1, //工作进程数量 19 | //'daemonize' => true, //是否作为守护进程 20 | 'daemonize' => false, //是否作为守护进程 21 | 'ssl_cert_file' => $sslinfo['ssl_cert_file'], 22 | 'ssl_key_file' => $sslinfo['ssl_key_file'], 23 | )); 24 | 25 | $serv->reqproxylist = array(); 26 | $serv->reglist = array(); 27 | $serv->sockinfolist = array(); 28 | 29 | $serv->on('connect', 'NGROK::connect'); 30 | $serv->on('receive', 'NGROK::receive'); 31 | $serv->on('close', 'NGROK::close'); 32 | $serv->start(); 33 | ?> --------------------------------------------------------------------------------