├── README.md
├── natapp.php
├── ngrokclient.php
└── sunny.php
/README.md:
--------------------------------------------------------------------------------
1 | ## ngrok-php
2 | 一个php的ngrok客户端,不需要依赖啥扩展。。php5.2以上都可以跑,支持cgi模式,跟cli模式,意味着你在nginx/apache里面跑,特方便,代码很简单只有500行,比较讽刺的是,貌似性能比C语言版本还好,可见我的C语言有多烂。。
3 |
4 | 还有个hauntek大神写的python版本,https://github.com/hauntek/python-ngrok
5 |
6 | # 运行方法
7 | 直接运行就行了。。
8 |
9 | # 温馨提示
10 | 鉴于长时间跑cgi程序,可能php不怎么稳定,所以里面有个自杀函数。。1个小时会自动退出,最好跟任务计划配合。。这样就可以随时。。连接了。。
11 | ,也可以去掉那函数。
12 |
13 | 以下来自hauntek大神的分支。
14 |
15 | # 适配平台
16 |
17 | ***
18 | 1.ngrok.cc[sunny.php]
19 |
20 | 2.natapp.cn[natapp.php]
21 |
22 | 适配平台运行方法,文件内有说明
23 |
24 | ***
25 |
26 | ## 更新日记 v1.38(2017/03/13)
27 |
28 | ***
29 |
30 | 1.支持ngrok.cc https协议头
31 |
32 | 2.优化隧道查询信息判断
33 |
34 | ***
35 |
36 | ## 更新日记 v1.36(2016/08/08)
37 |
38 | ***
39 |
40 | 1.删除无用的函数
41 |
42 | 2.优化渠道注册成功判断
43 |
44 | 3.去除没必要清理的循环变量
45 |
46 | 4.去除建立本地渠道的解析判断
47 |
48 | 5.修复本地渠道建立失败无需覆盖
49 |
50 | 6.添加是否允许自签名证书上下文
51 |
52 | ***
53 |
54 | ## 更新日记 v1.33(2016/08/02)
55 |
56 | ***
57 |
58 | 1.修复首次运行处于断网状态,渠道反复注册
59 |
60 | 2.修复退出时close错误
61 |
62 | ***
63 |
64 | ## 更新日记 v1.32(2016/07/31)
65 |
66 | ***
67 |
68 | 1.添加域名解析ip后再进行连接操作,避免链接不上循环问题导致cpu过高
69 |
70 | 2.添加屏蔽警告错误
71 |
72 | 3.修复断线重连判断优化,优先判断dns
73 |
74 | 4.修复close不是资源变量删除并跳出当前循环
75 |
76 | 5.修复输出日记乱码修复
77 |
78 | 6.修复隧道已注册后无限循环注册的问题,并且等待60秒后继续注册
79 |
80 | 7.添加本地映射地址无效转向定制的html页面
81 |
82 | 8.添加连接状态日记提醒
83 |
84 | ***
85 |
--------------------------------------------------------------------------------
/natapp.php:
--------------------------------------------------------------------------------
1 | $mainsocket, 'linkstate' => 0, 'type' => 1);
60 | }
61 |
62 | //注册退出执行函数
63 | register_shutdown_function('shutdown');
64 | while ($recvflag) {
65 |
66 | //重排
67 | array_filter($socklist);
68 | sort($socklist);
69 |
70 | //检测控制连接是否连接.
71 | if ($mainsocket == false) {
72 | $ip = dnsopen($seraddr); //解析dns
73 | if (!$ip) {
74 | ConsoleOut('连接natapp服务器失败.');
75 | sleep(1);
76 | continue;
77 | }
78 | $mainsocket = connectremote($ip, $port);
79 | if(!$mainsocket) {
80 | ConsoleOut('连接natapp服务器失败.');
81 | sleep(10);
82 | continue;
83 | }
84 | $socklist[] = array('sock' => $mainsocket, 'linkstate' => 0, 'type' => 1);
85 | }
86 |
87 | //如果非cli超过1小时自杀
88 | if (is_cli() == false) {
89 | if ($starttime + 3600 < time()) {
90 | fclose($mainsocket);
91 | $recvflag = false;
92 | break;
93 | }
94 | }
95 |
96 | //发送心跳
97 | if ($pingtime + 25 < time() && $pingtime != 0) {
98 | sendpack($mainsocket, Ping());
99 | $pingtime = time();
100 | }
101 |
102 | //重新赋值
103 | $readfds = array();
104 | $writefds = array();
105 | foreach ($socklist as $k => $z) {
106 | if (is_resource($z['sock'])) {
107 | $readfds[] = $z['sock'];
108 | if ($z['linkstate'] == 0) {
109 | $writefds[] = $z['sock'];
110 | }
111 | } else {
112 | //close的时候不是资源。。移除
113 | if ($z['type'] == 1) {
114 | $mainsocket = false;
115 | }
116 | array_splice($socklist, $k, 1);
117 | }
118 | }
119 |
120 | //查询
121 | $res = stream_select($readfds, $writefds, $e, $t);
122 | if ($res === false) {
123 | ConsoleOut('sockerr', 'debug');
124 | }
125 |
126 | //有事件
127 | if ($res > 0) {
128 | foreach ($socklist as $k => $sockinfo) {
129 | $sock = $sockinfo['sock'];
130 | //可读
131 | if (in_array($sock, $readfds)) {
132 |
133 | $recvbut = fread($sock, 1024);
134 |
135 | if ($recvbut == false || strlen($recvbut) == 0) {
136 | //主连接关闭,关闭所有
137 | if ($sockinfo['type'] == 1) {
138 | $mainsocket = false;
139 | }
140 | if ($sockinfo['type'] == 3) {
141 | fclose($sockinfo['tosock']);
142 | }
143 | unset($socklist[$k]);
144 | continue;
145 | }
146 |
147 | if (strlen($recvbut) > 0) {
148 | if (!isset($sockinfo['recvbuf'])) {
149 | $sockinfo['recvbuf'] = $recvbut;
150 | } else {
151 | $sockinfo['recvbuf'] = $sockinfo['recvbuf'] . $recvbut;
152 | }
153 | $socklist[$k] = $sockinfo;
154 | }
155 |
156 | //控制连接,或者远程未连接本地连接
157 | if ($sockinfo['type'] == 1 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 1)) {
158 | $allrecvbut = $sockinfo['recvbuf'];
159 | //处理
160 | $lenbuf = substr($allrecvbut, 0, 8);
161 | $len = tolen1($lenbuf);
162 | if (strlen($allrecvbut) >= (8 + $len)) {
163 | $json = substr($allrecvbut, 8, $len);
164 | ConsoleOut($json, 'debug');
165 | $js = json_decode($json, true);
166 |
167 | //远程主连接
168 | if ($sockinfo['type'] == 1) {
169 | if ($js['Type'] == 'ReqProxy') {
170 | $newsock = connectremote($seraddr, $port);
171 | if ($newsock) {
172 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 2);
173 | }
174 | }
175 | if ($js['Type'] == 'AuthResp') {
176 | if ($js['Payload']['Error'] != null) {
177 | ConsoleOut($js['Payload']['Error']);
178 | sleep(30);
179 | exit();
180 | }
181 | $ClientId = $js['Payload']['ClientId'];
182 | $pingtime = time();
183 | sendpack($sock, Ping());
184 | }
185 | if ($js['Type'] == 'NewTunnel') {
186 | $Tunnels[$js['Payload']['Url']] = $js['Payload'];
187 | ConsoleOut('隧道建立成功:' . $js['Payload']['Url']);
188 | }
189 | }
190 |
191 | //远程代理连接
192 | if ($sockinfo['type'] == 2) {
193 | //未连接本地
194 | if ($sockinfo['linkstate'] == 1) {
195 | if ($js['Type'] == 'StartProxy') {
196 | $loacladdr = getloacladdr($Tunnels, $js['Payload']['Url']);
197 |
198 | $newsock = connectlocal($loacladdr[0], $loacladdr[1]);
199 | if ($newsock) {
200 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 3, 'tosock' => $sock);
201 | //把本地连接覆盖上去
202 | $sockinfo['tosock'] = $newsock;
203 | $sockinfo['linkstate'] = 2;
204 | } else {
205 | $body = '
Web服务错误隧道 %s 无效
无法连接到%s. 此端口尚未提供Web服务
';
206 | $html = sprintf($body, $js['Payload']['Url'], $loacladdr[0] .':' . $loacladdr[1]);
207 | $header = "HTTP/1.0 502 Bad Gateway"."\r\n";
208 | $header .= "Content-Type: text/html"."\r\n";
209 | $header .= "Content-Length: %d"."\r\n";
210 | $header .= "\r\n"."%s";
211 | $buf = sprintf($header, strlen($html), $html);
212 | sendbuf($sock, $buf);
213 | }
214 | }
215 | }
216 | }
217 | //edit buffer
218 | if (strlen($allrecvbut) == (8 + $len)) {
219 | $sockinfo['recvbuf'] = '';
220 | } else {
221 | $sockinfo['recvbuf'] = substr($allrecvbut, 8 + $len);
222 | }
223 | $socklist[$k] = $sockinfo;
224 | }
225 | }
226 |
227 | //远程连接已连接本地跟本地连接,纯转发
228 | if ($sockinfo['type'] == 3 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 2)) {
229 | sendbuf($sockinfo['tosock'], $sockinfo['recvbuf']);
230 | $sockinfo['recvbuf'] = '';
231 | $socklist[$k] = $sockinfo;
232 | }
233 | }
234 |
235 | //可写
236 | if (in_array($sock, $writefds)) {
237 | if ($sockinfo['linkstate'] == 0) {
238 |
239 | if ($sockinfo['type'] == 1) {
240 | sendpack($sock, NgrokAuth($options), false);
241 | $sockinfo['linkstate'] = 1;
242 | $socklist[$k] = $sockinfo;
243 | }
244 | if ($sockinfo['type'] == 2) {
245 | sendpack($sock, RegProxy($ClientId), false);
246 | $sockinfo['linkstate'] = 1;
247 | $socklist[$k] = $sockinfo;
248 | }
249 | if ($sockinfo['type'] == 3) {
250 | $sockinfo['linkstate'] = 1;
251 | $socklist[$k] = $sockinfo;
252 | }
253 | }
254 | }
255 | }
256 | }
257 | }
258 |
259 | /* 域名解析 */
260 | function dnsopen($host) {
261 | $ip = gethostbyname($host); //解析dns
262 | if (!filter_var($ip, FILTER_VALIDATE_IP)) {
263 | return false;
264 | }
265 | return $ip;
266 | }
267 |
268 | /* 连接到远程 */
269 | function connectremote($seraddr, $port) {
270 | global $is_verify_peer;
271 | $socket = stream_socket_client('tcp://' . $seraddr . ':' . $port, $errno, $errstr, 30);
272 | if (!$socket) {
273 | return false;
274 | }
275 | //设置加密连接,默认是ssl,如果需要tls连接,可以查看php手册stream_socket_enable_crypto函数的解释
276 | if ($is_verify_peer == false) {
277 | stream_context_set_option($socket, 'ssl', 'verify_host', false);
278 | stream_context_set_option($socket, 'ssl', 'verify_peer_name', false);
279 | stream_context_set_option($socket, 'ssl', 'verify_peer', false);
280 | stream_context_set_option($socket, 'ssl', 'allow_self_signed', false);
281 | }
282 | stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
283 | stream_set_blocking($socket, 0); //设置为非阻塞模式
284 | return $socket;
285 | }
286 |
287 | /* 连接到本地 */
288 | function connectlocal($localaddr, $localport) {
289 | $socket = stream_socket_client('tcp://' . $localaddr . ':' . $localport, $errno, $errstr, 30);
290 | if (!$socket) {
291 | return false;
292 | }
293 | stream_set_blocking($socket, 0); //设置为非阻塞模式
294 | return $socket;
295 | }
296 |
297 | function getloacladdr($Tunnels, $url) {
298 | $proto = explode(':', $Tunnels[$url]['LocalAddr']);
299 | return $proto;
300 | }
301 |
302 | function NgrokAuth($token) {
303 | $Payload = array(
304 | 'ClientId' => '',
305 | 'OS' => 'php',
306 | 'Arch' => 'amd64',
307 | 'Version' => '4',
308 | 'MmVersion' => '2.1',
309 | 'User' => 'user',
310 | 'Password' => '',
311 | 'AuthToken' => $token['authtoken'],
312 | 'ClientToken' => $token['clienttoken'],
313 | );
314 | $json = array(
315 | 'Type' => 'Auth',
316 | 'Payload' => $Payload,
317 | );
318 | return json_encode($json);
319 | }
320 |
321 | function RegProxy($ClientId) {
322 | $Payload = array('ClientId' => $ClientId);
323 | $json = array(
324 | 'Type' => 'RegProxy',
325 | 'Payload' => $Payload,
326 | );
327 | return json_encode($json);
328 | }
329 |
330 | function Ping() {
331 | $Payload = (object) array();
332 | $json = array(
333 | 'Type' => 'Ping',
334 | 'Payload' => $Payload,
335 | );
336 | return json_encode($json);
337 | }
338 |
339 | /* 网络字节序 (只支持整型范围) */
340 | function lentobyte($len) {
341 | $xx = pack("N", $len);
342 | $xx1 = pack("C4", 0, 0, 0, 0);
343 | return $xx1 . $xx;
344 | }
345 |
346 | /* 机器字节序 (小端 只支持整型范围) */
347 | function lentobyte1($len) {
348 | $xx = pack("L", $len);
349 | $xx1 = pack("C4", 0, 0, 0, 0);
350 | return $xx . $xx1;
351 | }
352 |
353 | function sendpack($sock, $msg, $isblock = true) {
354 | if ($isblock) {
355 | stream_set_blocking($sock, 1); //设置为非阻塞模式
356 | }
357 | fwrite($sock, lentobyte1(strlen($msg)) . $msg);
358 | if ($isblock) {
359 | stream_set_blocking($sock, 0); //设置为非阻塞模式
360 | }
361 | }
362 |
363 | function sendbuf($sock, $buf, $isblock = true) {
364 | if ($isblock) {
365 | stream_set_blocking($sock, 1); //设置为非阻塞模式
366 | }
367 | fwrite($sock, $buf);
368 | if ($isblock) {
369 | stream_set_blocking($sock, 0); //设置为非阻塞模式
370 | }
371 | }
372 |
373 | /* 网络字节序 (只支持整型范围) */
374 | function tolen($v) {
375 | $array = unpack("N", $v);
376 | return $array[1];
377 | }
378 |
379 | /* 机器字节序 (小端) 只支持整型范围 */
380 | function tolen1($v) {
381 | $array = unpack("L", $v);
382 | return $array[1];
383 | }
384 |
385 | //输出日记到命令行
386 | function ConsoleOut($log, $level = 'info') {
387 | global $isDebug;
388 | if ($level == 'debug' and $isDebug == false) {
389 | return;
390 | }
391 | //cli
392 | if (is_cli()) {
393 | if (DIRECTORY_SEPARATOR == "\\") {
394 | $log = iconv('UTF-8', 'GB2312', $log);
395 | }
396 | echo $log . "\r\n";
397 | }
398 | //web
399 | else {
400 | echo $log . "
";
401 | ob_flush();
402 | flush();
403 | // file_put_contents("ngrok.log", date("Y-m-d H:i:s:::") . $log . "\r\n", FILE_APPEND);
404 | }
405 | }
406 |
407 | //判断是否命令行运行
408 | function is_cli() {
409 | return (php_sapi_name() === 'cli') ? true : false;
410 | }
411 |
412 | //natapp.cn 获取服务器设置
413 | function natapp_auth($token) {
414 | global $is_verify_peer;
415 | $host = 'auth.natapp.cn';
416 | $port = 443;
417 |
418 | $fp = stream_socket_client('tcp://' . $host . ':' . $port, $errno, $errstr, 10);
419 | if (!$fp) {
420 | ConsoleOut('连接认证服务器: https://auth.natapp.cn 错误.');
421 | sleep(10);
422 | exit();
423 | }
424 | if ($is_verify_peer == false) {
425 | stream_context_set_option($fp, 'ssl', 'verify_host', false);
426 | stream_context_set_option($fp, 'ssl', 'verify_peer_name', false);
427 | stream_context_set_option($fp, 'ssl', 'verify_peer', false);
428 | stream_context_set_option($fp, 'ssl', 'allow_self_signed', false);
429 | }
430 | stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
431 |
432 | $data = array(
433 | 'Authtoken' => $token['authtoken'],
434 | 'Clienttoken' => $token['clienttoken'],
435 | 'Token' => 'fffeephptokenkhd672',
436 | );
437 | $query = json_encode($data);
438 |
439 | $header = "POST "."/auth"." HTTP/1.1"."\r\n";
440 | $header .= "Content-Type: text/html"."\r\n";
441 | $header .= "Host: %s"."\r\n";
442 | $header .= "Content-Length: %d"."\r\n";
443 | $header .= "\r\n"."%s";
444 | $buf = sprintf($header, $host, strlen($query), $query);
445 | $write = fputs($fp, $buf);
446 |
447 | $body = null;
448 | while (!feof($fp)) {
449 | $line = fgets($fp, 1024); //去除请求包的头只显示页面的返回数据
450 | if ($line == "\n" || $line == "\r\n") {
451 | $chunk_size = (integer) hexdec(fgets($fp, 1024));
452 | if ($chunk_size > 0) {
453 | $body = fread($fp, $chunk_size);
454 | break;
455 | }
456 | }
457 | }
458 |
459 | fclose($fp);
460 | $authData = json_decode($body, true);
461 |
462 | if ($authData['success'] == false) {
463 | ConsoleOut('认证错误:' . $authData['msg'] . ' ErrorCode:' . $authData['errorCode']);
464 | sleep(10);
465 | exit();
466 | }
467 | ConsoleOut('认证成功,正在连接服务器...');
468 | $proto = explode(':', $authData['data']['ServerAddr']);
469 | return $proto;
470 | }
471 |
472 | //注册退出执行函数
473 | function shutdown() {
474 | global $mainsocket;
475 | sendpack($mainsocket, 'close');
476 | fclose($mainsocket);
477 | }
478 |
479 | ?>
480 |
--------------------------------------------------------------------------------
/ngrokclient.php:
--------------------------------------------------------------------------------
1 | 'http',
18 | 'hostname' => 'www.xxx.com',
19 | 'subdomain' => '',
20 | 'rport' => 0,
21 | 'lhost' => '127.0.0.1',
22 | 'lport' => 80
23 | ),
24 | array(
25 | 'protocol' => 'http',
26 | 'hostname' => '',
27 | 'subdomain' => 'xxx',
28 | 'rport' => 0,
29 | 'lhost' => '127.0.0.1',
30 | 'lport' => 80
31 | ),
32 | array(
33 | 'protocol' => 'tcp',
34 | 'hostname' => '',
35 | 'subdomain' => '',
36 | 'rport' => 57715,
37 | 'lhost' => '127.0.0.1',
38 | 'lport' => 22
39 | ),
40 |
41 | );
42 |
43 | //定义变量
44 | $readfds = array();
45 | $writefds = array();
46 |
47 | $e = null;
48 | $t = 1;
49 |
50 | $socklist = array();
51 |
52 | $ClientId = '';
53 | $recvflag = true;
54 | $starttime = time(); //启动时间
55 | $pingtime = 0;
56 |
57 | //建立隧道协议
58 | $mainsocket = connectremote($seraddr, $port);
59 | if($mainsocket) {
60 | $socklist[] = array('sock' => $mainsocket, 'linkstate' => 0, 'type' => 1);
61 | }
62 |
63 | //注册退出执行函数
64 | register_shutdown_function('shutdown');
65 | while ($recvflag) {
66 |
67 | //重排
68 | array_filter($socklist);
69 | sort($socklist);
70 |
71 | //检测控制连接是否连接.
72 | if ($mainsocket == false) {
73 | $ip = dnsopen($seraddr); //解析dns
74 | if (!$ip) {
75 | ConsoleOut('update dns');
76 | sleep(1);
77 | continue;
78 | }
79 | $mainsocket = connectremote($ip, $port);
80 | if(!$mainsocket) {
81 | ConsoleOut('connect failed...!');
82 | sleep(10);
83 | continue;
84 | }
85 | $socklist[] = array('sock' => $mainsocket, 'linkstate' => 0, 'type' => 1);
86 | }
87 |
88 | //如果非cli超过1小时自杀
89 | if (is_cli() == false) {
90 | if ($starttime + 3600 < time()) {
91 | fclose($mainsocket);
92 | $recvflag = false;
93 | break;
94 | }
95 | }
96 |
97 | //发送心跳
98 | if ($pingtime + 25 < time() && $pingtime != 0) {
99 | sendpack($mainsocket, Ping());
100 | $pingtime = time();
101 | }
102 |
103 | //重新赋值
104 | $readfds = array();
105 | $writefds = array();
106 | foreach ($socklist as $k => $z) {
107 | if (is_resource($z['sock'])) {
108 | $readfds[] = $z['sock'];
109 | if ($z['linkstate'] == 0) {
110 | $writefds[] = $z['sock'];
111 | }
112 | } else {
113 | //close的时候不是资源。。移除
114 | if ($z['type'] == 1) {
115 | $mainsocket = false;
116 | }
117 | array_splice($socklist, $k, 1);
118 | }
119 | }
120 |
121 | //查询
122 | $res = stream_select($readfds, $writefds, $e, $t);
123 | if ($res === false) {
124 | ConsoleOut('sockerr');
125 | }
126 |
127 | //有事件
128 | if ($res > 0) {
129 | foreach ($socklist as $k => $sockinfo) {
130 | $sock = $sockinfo['sock'];
131 | //可读
132 | if (in_array($sock, $readfds)) {
133 |
134 | $recvbut = fread($sock, 1024);
135 |
136 | if ($recvbut == false || strlen($recvbut) == 0) {
137 | //主连接关闭,关闭所有
138 | if ($sockinfo['type'] == 1) {
139 | $mainsocket = false;
140 | }
141 | if ($sockinfo['type'] == 3) {
142 | fclose($sockinfo['tosock']);
143 | }
144 | unset($socklist[$k]);
145 | continue;
146 | }
147 |
148 | if (strlen($recvbut) > 0) {
149 | if (!isset($sockinfo['recvbuf'])) {
150 | $sockinfo['recvbuf'] = $recvbut;
151 | } else {
152 | $sockinfo['recvbuf'] = $sockinfo['recvbuf'] . $recvbut;
153 | }
154 | $socklist[$k] = $sockinfo;
155 | }
156 |
157 | //控制连接,或者远程未连接本地连接
158 | if ($sockinfo['type'] == 1 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 1)) {
159 | $allrecvbut = $sockinfo['recvbuf'];
160 | //处理
161 | $lenbuf = substr($allrecvbut, 0, 8);
162 | $len = tolen1($lenbuf);
163 | if (strlen($allrecvbut) >= (8 + $len)) {
164 | $json = substr($allrecvbut, 8, $len);
165 | ConsoleOut($json);
166 | $js = json_decode($json, true);
167 |
168 | //远程主连接
169 | if ($sockinfo['type'] == 1) {
170 | if ($js['Type'] == 'ReqProxy') {
171 | $newsock = connectremote($seraddr, $port);
172 | if ($newsock) {
173 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 2);
174 | }
175 | }
176 | if ($js['Type'] == 'AuthResp') {
177 | $ClientId = $js['Payload']['ClientId'];
178 | $pingtime = time();
179 | sendpack($sock, Ping());
180 | foreach ($Tunnels as $tunnelinfo) {
181 | //注册端口
182 | sendpack($sock, ReqTunnel($tunnelinfo['protocol'], $tunnelinfo['hostname'], $tunnelinfo['subdomain'], $tunnelinfo['rport']));
183 | }
184 | }
185 | if ($js['Type'] == 'NewTunnel') {
186 | if ($js['Payload']['Error'] != null) {
187 | ConsoleOut('Add tunnel failed,' . $js['Payload']['Error']);
188 | sleep(30);
189 | } else {
190 | ConsoleOut('Add tunnel ok,type:' . $js['Payload']['Protocol'] . ' url:' . $js['Payload']['Url']);
191 | }
192 | }
193 | }
194 |
195 | //远程代理连接
196 | if ($sockinfo['type'] == 2) {
197 | //未连接本地
198 | if ($sockinfo['linkstate'] == 1) {
199 | if ($js['Type'] == 'StartProxy') {
200 | $loacladdr = getloacladdr($Tunnels, $js['Payload']['Url']);
201 |
202 | $newsock = connectlocal($loacladdr['lhost'], $loacladdr['lport']);
203 | if ($newsock) {
204 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 3, 'tosock' => $sock);
205 | //把本地连接覆盖上去
206 | $sockinfo['tosock'] = $newsock;
207 | $sockinfo['linkstate'] = 2;
208 | } else {
209 | $body = 'Web服务错误隧道 %s 无效
无法连接到%s. 此端口尚未提供Web服务
';
210 | $html = sprintf($body, $js['Payload']['Url'], $loacladdr['lhost'] .':' . $loacladdr['lport']);
211 | $header = "HTTP/1.0 502 Bad Gateway"."\r\n";
212 | $header .= "Content-Type: text/html"."\r\n";
213 | $header .= "Content-Length: %d"."\r\n";
214 | $header .= "\r\n"."%s";
215 | $buf = sprintf($header, strlen($html), $html);
216 | sendbuf($sock, $buf);
217 | }
218 | }
219 | }
220 | }
221 | //edit buffer
222 | if (strlen($allrecvbut) == (8 + $len)) {
223 | $sockinfo['recvbuf'] = '';
224 | } else {
225 | $sockinfo['recvbuf'] = substr($allrecvbut, 8 + $len);
226 | }
227 | $socklist[$k] = $sockinfo;
228 | }
229 | }
230 |
231 | //远程连接已连接本地跟本地连接,纯转发
232 | if ($sockinfo['type'] == 3 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 2)) {
233 | sendbuf($sockinfo['tosock'], $sockinfo['recvbuf']);
234 | $sockinfo['recvbuf'] = '';
235 | $socklist[$k] = $sockinfo;
236 | }
237 | }
238 |
239 | //可写
240 | if (in_array($sock, $writefds)) {
241 | if ($sockinfo['linkstate'] == 0) {
242 |
243 | if ($sockinfo['type'] == 1) {
244 | sendpack($sock, NgrokAuth(), false);
245 | $sockinfo['linkstate'] = 1;
246 | $socklist[$k] = $sockinfo;
247 | }
248 | if ($sockinfo['type'] == 2) {
249 | sendpack($sock, RegProxy($ClientId), false);
250 | $sockinfo['linkstate'] = 1;
251 | $socklist[$k] = $sockinfo;
252 | }
253 | if ($sockinfo['type'] == 3) {
254 | $sockinfo['linkstate'] = 1;
255 | $socklist[$k] = $sockinfo;
256 | }
257 | }
258 | }
259 | }
260 | }
261 | }
262 |
263 | /* 域名解析 */
264 | function dnsopen($host) {
265 | $ip = gethostbyname($host); //解析dns
266 | if (!filter_var($ip, FILTER_VALIDATE_IP)) {
267 | return false;
268 | }
269 | return $ip;
270 | }
271 |
272 | /* 连接到远程 */
273 | function connectremote($seraddr, $port) {
274 | global $is_verify_peer;
275 | $socket = stream_socket_client('tcp://' . $seraddr . ':' . $port, $errno, $errstr, 30);
276 | if (!$socket) {
277 | return false;
278 | }
279 | //设置加密连接,默认是ssl,如果需要tls连接,可以查看php手册stream_socket_enable_crypto函数的解释
280 | if ($is_verify_peer == false) {
281 | stream_context_set_option($socket, 'ssl', 'verify_host', false);
282 | stream_context_set_option($socket, 'ssl', 'verify_peer_name', false);
283 | stream_context_set_option($socket, 'ssl', 'verify_peer', false);
284 | stream_context_set_option($socket, 'ssl', 'allow_self_signed', false);
285 | }
286 | stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
287 | stream_set_blocking($socket, 0); //设置为非阻塞模式
288 | return $socket;
289 | }
290 |
291 | /* 连接到本地 */
292 | function connectlocal($localaddr, $localport) {
293 | $socket = stream_socket_client('tcp://' . $localaddr . ':' . $localport, $errno, $errstr, 30);
294 | if (!$socket) {
295 | return false;
296 | }
297 | stream_set_blocking($socket, 0); //设置为非阻塞模式
298 | return $socket;
299 | }
300 |
301 | function getloacladdr($Tunnels, $url) {
302 | $protocol = substr($url, 0, strpos($url, ':'));
303 | $hostname = substr($url, strpos($url, '//') + 2);
304 | $subdomain = trim(substr($hostname, 0, strpos($hostname, '.')));
305 | $rport = substr($url, strrpos($url, ':') + 1);
306 |
307 | // echo 'protocol:'.$protocol."\r\n";
308 | // echo '$subdomain:'.$subdomain."\r\n";
309 | // echo '$hostname:'.$hostname."\r\n";
310 | // echo '$rport:'.$rport."\r\n";
311 |
312 | foreach ($Tunnels as $k => $z) {
313 | //
314 | if ($protocol == $z['protocol']) {
315 | if ($hostname == $z['hostname']) {
316 | return $z;
317 | }
318 | if ($subdomain == $z['subdomain']) {
319 | return $z;
320 | }
321 | }
322 | if ($protocol == 'tcp') {
323 | if ($rport == $z['rport']) {
324 | return $z;
325 | }
326 | }
327 | }
328 | // array('protocol'=>$protocol,'hostname'=>'','subdomain'=>'','rport'=>0,'lhost'=>'','lport'=>80),
329 | }
330 |
331 | function NgrokAuth() {
332 | $Payload = array(
333 | 'ClientId' => '',
334 | 'OS' => 'darwin',
335 | 'Arch' => 'amd64',
336 | 'Version' => '2',
337 | 'MmVersion' => '1.7',
338 | 'User' => 'user',
339 | 'Password' => '',
340 | );
341 | $json = array(
342 | 'Type' => 'Auth',
343 | 'Payload' => $Payload,
344 | );
345 | return json_encode($json);
346 | }
347 |
348 | function ReqTunnel($protocol, $HostName, $Subdomain, $RemotePort) {
349 | $Payload = array(
350 | 'ReqId' => getRandChar(8),
351 | 'Protocol' => $protocol,
352 | 'Hostname' => $HostName,
353 | 'Subdomain' => $Subdomain,
354 | 'HttpAuth' => '',
355 | 'RemotePort' => $RemotePort,
356 | );
357 | $json = array(
358 | 'Type' => 'ReqTunnel',
359 | 'Payload' => $Payload,
360 | );
361 | return json_encode($json);
362 | }
363 |
364 | function RegProxy($ClientId) {
365 | $Payload = array('ClientId' => $ClientId);
366 | $json = array(
367 | 'Type' => 'RegProxy',
368 | 'Payload' => $Payload,
369 | );
370 | return json_encode($json);
371 | }
372 |
373 | function Ping() {
374 | $Payload = (object) array();
375 | $json = array(
376 | 'Type' => 'Ping',
377 | 'Payload' => $Payload,
378 | );
379 | return json_encode($json);
380 | }
381 |
382 | /* 网络字节序 (只支持整型范围) */
383 | function lentobyte($len) {
384 | $xx = pack("N", $len);
385 | $xx1 = pack("C4", 0, 0, 0, 0);
386 | return $xx1 . $xx;
387 | }
388 |
389 | /* 机器字节序 (小端 只支持整型范围) */
390 | function lentobyte1($len) {
391 | $xx = pack("L", $len);
392 | $xx1 = pack("C4", 0, 0, 0, 0);
393 | return $xx . $xx1;
394 | }
395 |
396 | function sendpack($sock, $msg, $isblock = true) {
397 | if ($isblock) {
398 | stream_set_blocking($sock, 1); //设置为非阻塞模式
399 | }
400 | fwrite($sock, lentobyte1(strlen($msg)) . $msg);
401 | if ($isblock) {
402 | stream_set_blocking($sock, 0); //设置为非阻塞模式
403 | }
404 | }
405 |
406 | function sendbuf($sock, $buf, $isblock = true) {
407 | if ($isblock) {
408 | stream_set_blocking($sock, 1); //设置为非阻塞模式
409 | }
410 | fwrite($sock, $buf);
411 | if ($isblock) {
412 | stream_set_blocking($sock, 0); //设置为非阻塞模式
413 | }
414 | }
415 |
416 | /* 网络字节序 (只支持整型范围) */
417 | function tolen($v) {
418 | $array = unpack("N", $v);
419 | return $array[1];
420 | }
421 |
422 | /* 机器字节序 (小端) 只支持整型范围 */
423 | function tolen1($v) {
424 | $array = unpack("L", $v);
425 | return $array[1];
426 | }
427 |
428 | //随机生成字符串
429 | function getRandChar($length) {
430 | $str = null;
431 | $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
432 | $max = strlen($strPol) - 1;
433 |
434 | for ($i = 0; $i < $length; $i++) {
435 | $str .= $strPol[rand(0, $max)];
436 | }
437 |
438 | return $str;
439 | }
440 |
441 | //输出日记到命令行
442 | function ConsoleOut($log) {
443 | //cli
444 | if (is_cli()) {
445 | if (DIRECTORY_SEPARATOR == "\\") {
446 | $log = iconv('UTF-8', 'GB2312', $log);
447 | }
448 | echo $log . "\r\n";
449 | }
450 | //web
451 | else {
452 | echo $log . "
";
453 | ob_flush();
454 | flush();
455 | // file_put_contents("ngrok.log", date("Y-m-d H:i:s:::") . $log . "\r\n", FILE_APPEND);
456 | }
457 | }
458 |
459 | //判断是否命令行运行
460 | function is_cli() {
461 | return (php_sapi_name() === 'cli') ? true : false;
462 | }
463 |
464 | //注册退出执行函数
465 | function shutdown() {
466 | global $mainsocket;
467 | sendpack($mainsocket, 'close');
468 | fclose($mainsocket);
469 | }
470 |
471 | ?>
472 |
--------------------------------------------------------------------------------
/sunny.php:
--------------------------------------------------------------------------------
1 | $mainsocket, 'linkstate' => 0, 'type' => 1);
60 | }
61 |
62 | //注册退出执行函数
63 | register_shutdown_function('shutdown');
64 | while ($recvflag) {
65 |
66 | //重排
67 | array_filter($socklist);
68 | sort($socklist);
69 |
70 | //检测控制连接是否连接.
71 | if ($mainsocket == false) {
72 | $ip = dnsopen($seraddr); //解析dns
73 | if (!$ip) {
74 | ConsoleOut('连接ngrok服务器失败.');
75 | sleep(1);
76 | continue;
77 | }
78 | $mainsocket = connectremote($ip, $port);
79 | if(!$mainsocket) {
80 | ConsoleOut('连接ngrok服务器失败.');
81 | sleep(10);
82 | continue;
83 | }
84 | $socklist[] = array('sock' => $mainsocket, 'linkstate' => 0, 'type' => 1);
85 | }
86 |
87 | //如果非cli超过1小时自杀
88 | if (is_cli() == false) {
89 | if ($starttime + 3600 < time()) {
90 | fclose($mainsocket);
91 | $recvflag = false;
92 | break;
93 | }
94 | }
95 |
96 | //发送心跳
97 | if ($pingtime + 25 < time() && $pingtime != 0) {
98 | sendpack($mainsocket, Ping());
99 | $pingtime = time();
100 | }
101 |
102 | //重新赋值
103 | $readfds = array();
104 | $writefds = array();
105 | foreach ($socklist as $k => $z) {
106 | if (is_resource($z['sock'])) {
107 | $readfds[] = $z['sock'];
108 | if ($z['linkstate'] == 0) {
109 | $writefds[] = $z['sock'];
110 | }
111 | } else {
112 | //close的时候不是资源。。移除
113 | if ($z['type'] == 1) {
114 | $mainsocket = false;
115 | }
116 | array_splice($socklist, $k, 1);
117 | }
118 | }
119 |
120 | //查询
121 | $res = stream_select($readfds, $writefds, $e, $t);
122 | if ($res === false) {
123 | ConsoleOut('sockerr', 'debug');
124 | }
125 |
126 | //有事件
127 | if ($res > 0) {
128 | foreach ($socklist as $k => $sockinfo) {
129 | $sock = $sockinfo['sock'];
130 | //可读
131 | if (in_array($sock, $readfds)) {
132 |
133 | $recvbut = fread($sock, 1024);
134 |
135 | if ($recvbut == false || strlen($recvbut) == 0) {
136 | //主连接关闭,关闭所有
137 | if ($sockinfo['type'] == 1) {
138 | $mainsocket = false;
139 | }
140 | if ($sockinfo['type'] == 3) {
141 | fclose($sockinfo['tosock']);
142 | }
143 | unset($socklist[$k]);
144 | continue;
145 | }
146 |
147 | if (strlen($recvbut) > 0) {
148 | if (!isset($sockinfo['recvbuf'])) {
149 | $sockinfo['recvbuf'] = $recvbut;
150 | } else {
151 | $sockinfo['recvbuf'] = $sockinfo['recvbuf'] . $recvbut;
152 | }
153 | $socklist[$k] = $sockinfo;
154 | }
155 |
156 | //控制连接,或者远程未连接本地连接
157 | if ($sockinfo['type'] == 1 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 1)) {
158 | $allrecvbut = $sockinfo['recvbuf'];
159 | //处理
160 | $lenbuf = substr($allrecvbut, 0, 8);
161 | $len = tolen1($lenbuf);
162 | if (strlen($allrecvbut) >= (8 + $len)) {
163 | $json = substr($allrecvbut, 8, $len);
164 | ConsoleOut($json, 'debug');
165 | $js = json_decode($json, true);
166 |
167 | //远程主连接
168 | if ($sockinfo['type'] == 1) {
169 | if ($js['Type'] == 'ReqProxy') {
170 | $newsock = connectremote($seraddr, $port);
171 | if ($newsock) {
172 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 2);
173 | }
174 | }
175 | if ($js['Type'] == 'AuthResp') {
176 | $ClientId = $js['Payload']['ClientId'];
177 | $pingtime = time();
178 | sendpack($sock, Ping());
179 | foreach ($Tunnels as $tunnelinfo) {
180 | //注册端口
181 | sendpack($sock, ReqTunnel($tunnelinfo['protocol'], $tunnelinfo['hostname'], $tunnelinfo['subdomain'], $tunnelinfo['httpauth'], $tunnelinfo['rport']));
182 | }
183 | }
184 | if ($js['Type'] == 'NewTunnel') {
185 | if ($js['Payload']['Error'] != null) {
186 | ConsoleOut('隧道建立失败:' . $js['Payload']['Error']);
187 | sleep(30);
188 | } else {
189 | ConsoleOut('隧道建立成功:' . $js['Payload']['Url']);
190 | }
191 | }
192 | }
193 |
194 | //远程代理连接
195 | if ($sockinfo['type'] == 2) {
196 | //未连接本地
197 | if ($sockinfo['linkstate'] == 1) {
198 | if ($js['Type'] == 'StartProxy') {
199 | $loacladdr = getloacladdr($Tunnels, $js['Payload']['Url']);
200 |
201 | $newsock = connectlocal($loacladdr['lhost'], $loacladdr['lport']);
202 | if ($newsock) {
203 | $socklist[] = array('sock' => $newsock, 'linkstate' => 0, 'type' => 3, 'tosock' => $sock);
204 | //把本地连接覆盖上去
205 | $sockinfo['tosock'] = $newsock;
206 | $sockinfo['linkstate'] = 2;
207 | } else {
208 | $body = 'Web服务错误隧道 %s 无效
无法连接到%s. 此端口尚未提供Web服务
';
209 | $html = sprintf($body, $js['Payload']['Url'], $loacladdr['lhost'] .':' . $loacladdr['lport']);
210 | $header = "HTTP/1.0 502 Bad Gateway"."\r\n";
211 | $header .= "Content-Type: text/html"."\r\n";
212 | $header .= "Content-Length: %d"."\r\n";
213 | $header .= "\r\n"."%s";
214 | $buf = sprintf($header, strlen($html), $html);
215 | sendbuf($sock, $buf);
216 | }
217 | }
218 | }
219 | }
220 | //edit buffer
221 | if (strlen($allrecvbut) == (8 + $len)) {
222 | $sockinfo['recvbuf'] = '';
223 | } else {
224 | $sockinfo['recvbuf'] = substr($allrecvbut, 8 + $len);
225 | }
226 | $socklist[$k] = $sockinfo;
227 | }
228 | }
229 |
230 | //远程连接已连接本地跟本地连接,纯转发
231 | if ($sockinfo['type'] == 3 || ($sockinfo['type'] == 2 && $sockinfo['linkstate'] == 2)) {
232 | sendbuf($sockinfo['tosock'], $sockinfo['recvbuf']);
233 | $sockinfo['recvbuf'] = '';
234 | $socklist[$k] = $sockinfo;
235 | }
236 | }
237 |
238 | //可写
239 | if (in_array($sock, $writefds)) {
240 | if ($sockinfo['linkstate'] == 0) {
241 |
242 | if ($sockinfo['type'] == 1) {
243 | sendpack($sock, NgrokAuth(), false);
244 | $sockinfo['linkstate'] = 1;
245 | $socklist[$k] = $sockinfo;
246 | }
247 | if ($sockinfo['type'] == 2) {
248 | sendpack($sock, RegProxy($ClientId), false);
249 | $sockinfo['linkstate'] = 1;
250 | $socklist[$k] = $sockinfo;
251 | }
252 | if ($sockinfo['type'] == 3) {
253 | $sockinfo['linkstate'] = 1;
254 | $socklist[$k] = $sockinfo;
255 | }
256 | }
257 | }
258 | }
259 | }
260 | }
261 |
262 | /* 域名解析 */
263 | function dnsopen($host) {
264 | $ip = gethostbyname($host); //解析dns
265 | if (!filter_var($ip, FILTER_VALIDATE_IP)) {
266 | return false;
267 | }
268 | return $ip;
269 | }
270 |
271 | /* 连接到远程 */
272 | function connectremote($seraddr, $port) {
273 | global $is_verify_peer;
274 | $socket = stream_socket_client('tcp://' . $seraddr . ':' . $port, $errno, $errstr, 30);
275 | if (!$socket) {
276 | return false;
277 | }
278 | //设置加密连接,默认是ssl,如果需要tls连接,可以查看php手册stream_socket_enable_crypto函数的解释
279 | if ($is_verify_peer == false) {
280 | stream_context_set_option($socket, 'ssl', 'verify_host', false);
281 | stream_context_set_option($socket, 'ssl', 'verify_peer_name', false);
282 | stream_context_set_option($socket, 'ssl', 'verify_peer', false);
283 | stream_context_set_option($socket, 'ssl', 'allow_self_signed', false);
284 | }
285 | stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
286 | stream_set_blocking($socket, 0); //设置为非阻塞模式
287 | return $socket;
288 | }
289 |
290 | /* 连接到本地 */
291 | function connectlocal($localaddr, $localport) {
292 | $socket = stream_socket_client('tcp://' . $localaddr . ':' . $localport, $errno, $errstr, 30);
293 | if (!$socket) {
294 | return false;
295 | }
296 | stream_set_blocking($socket, 0); //设置为非阻塞模式
297 | return $socket;
298 | }
299 |
300 | function getloacladdr($Tunnels, $url) {
301 | $protocol = substr($url, 0, strpos($url, ':'));
302 | $hostname = substr($url, strpos($url, '//') + 2);
303 | $subdomain = trim(substr($hostname, 0, strpos($hostname, '.')));
304 | $rport = substr($url, strrpos($url, ':') + 1);
305 |
306 | // echo 'protocol:'.$protocol."\r\n";
307 | // echo '$subdomain:'.$subdomain."\r\n";
308 | // echo '$hostname:'.$hostname."\r\n";
309 | // echo '$rport:'.$rport."\r\n";
310 |
311 | foreach ($Tunnels as $k => $z) {
312 | //
313 | if ($protocol == $z['protocol']) {
314 | if ($hostname == $z['hostname']) {
315 | return $z;
316 | }
317 | if ($subdomain == $z['subdomain']) {
318 | return $z;
319 | }
320 | }
321 | if ($protocol == 'tcp') {
322 | if ($rport == $z['rport']) {
323 | return $z;
324 | }
325 | }
326 | }
327 | // array('protocol'=>$protocol,'hostname'=>'','subdomain'=>'','rport'=>0,'lhost'=>'','lport'=>80),
328 | }
329 |
330 | function NgrokAuth() {
331 | $Payload = array(
332 | 'ClientId' => '',
333 | 'OS' => 'darwin',
334 | 'Arch' => 'amd64',
335 | 'Version' => '2',
336 | 'MmVersion' => '2.1',
337 | 'User' => 'user',
338 | 'Password' => '',
339 | );
340 | $json = array(
341 | 'Type' => 'Auth',
342 | 'Payload' => $Payload,
343 | );
344 | return json_encode($json);
345 | }
346 |
347 | function ReqTunnel($protocol, $HostName, $Subdomain, $HttpAuth, $RemotePort) {
348 | $Payload = array(
349 | 'ReqId' => getRandChar(8),
350 | 'Protocol' => $protocol,
351 | 'Hostname' => $HostName,
352 | 'Subdomain' => $Subdomain,
353 | 'HttpAuth' => $HttpAuth,
354 | 'RemotePort' => $RemotePort,
355 | );
356 | $json = array(
357 | 'Type' => 'ReqTunnel',
358 | 'Payload' => $Payload,
359 | );
360 | return json_encode($json);
361 | }
362 |
363 | function RegProxy($ClientId) {
364 | $Payload = array('ClientId' => $ClientId);
365 | $json = array(
366 | 'Type' => 'RegProxy',
367 | 'Payload' => $Payload,
368 | );
369 | return json_encode($json);
370 | }
371 |
372 | function Ping() {
373 | $Payload = (object) array();
374 | $json = array(
375 | 'Type' => 'Ping',
376 | 'Payload' => $Payload,
377 | );
378 | return json_encode($json);
379 | }
380 |
381 | /* 网络字节序 (只支持整型范围) */
382 | function lentobyte($len) {
383 | $xx = pack("N", $len);
384 | $xx1 = pack("C4", 0, 0, 0, 0);
385 | return $xx1 . $xx;
386 | }
387 |
388 | /* 机器字节序 (小端 只支持整型范围) */
389 | function lentobyte1($len) {
390 | $xx = pack("L", $len);
391 | $xx1 = pack("C4", 0, 0, 0, 0);
392 | return $xx . $xx1;
393 | }
394 |
395 | function sendpack($sock, $msg, $isblock = true) {
396 | if ($isblock) {
397 | stream_set_blocking($sock, 1); //设置为非阻塞模式
398 | }
399 | fwrite($sock, lentobyte1(strlen($msg)) . $msg);
400 | if ($isblock) {
401 | stream_set_blocking($sock, 0); //设置为非阻塞模式
402 | }
403 | }
404 |
405 | function sendbuf($sock, $buf, $isblock = true) {
406 | if ($isblock) {
407 | stream_set_blocking($sock, 1); //设置为非阻塞模式
408 | }
409 | fwrite($sock, $buf);
410 | if ($isblock) {
411 | stream_set_blocking($sock, 0); //设置为非阻塞模式
412 | }
413 | }
414 |
415 | /* 网络字节序 (只支持整型范围) */
416 | function tolen($v) {
417 | $array = unpack("N", $v);
418 | return $array[1];
419 | }
420 |
421 | /* 机器字节序 (小端) 只支持整型范围 */
422 | function tolen1($v) {
423 | $array = unpack("L", $v);
424 | return $array[1];
425 | }
426 |
427 | //随机生成字符串
428 | function getRandChar($length) {
429 | $str = null;
430 | $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
431 | $max = strlen($strPol) - 1;
432 |
433 | for ($i = 0; $i < $length; $i++) {
434 | $str .= $strPol[rand(0, $max)];
435 | }
436 |
437 | return $str;
438 | }
439 |
440 | //输出日记到命令行
441 | function ConsoleOut($log, $level = 'info') {
442 | global $isDebug;
443 | if ($level == 'debug' and $isDebug == false) {
444 | return;
445 | }
446 | //cli
447 | if (is_cli()) {
448 | if (DIRECTORY_SEPARATOR == "\\") {
449 | $log = iconv('UTF-8', 'GB2312', $log);
450 | }
451 | echo $log . "\r\n";
452 | }
453 | //web
454 | else {
455 | echo $log . "
";
456 | ob_flush();
457 | flush();
458 | // file_put_contents("ngrok.log", date("Y-m-d H:i:s:::") . $log . "\r\n", FILE_APPEND);
459 | }
460 | }
461 |
462 | //判断是否命令行运行
463 | function is_cli() {
464 | return (php_sapi_name() === 'cli') ? true : false;
465 | }
466 |
467 | //ngrok.cc 获取服务器设置
468 | function ngrok_auth($clientid) {
469 | global $is_verify_peer;
470 | $host = 'www.ngrok.cc';
471 | $port = 443;
472 |
473 | $fp = stream_socket_client('tcp://' . $host . ':' . $port, $errno, $errstr, 10);
474 | if (!$fp) {
475 | ConsoleOut('连接认证服务器: https://www.ngrok.cc 错误.');
476 | sleep(10);
477 | exit();
478 | }
479 | if ($is_verify_peer == false) {
480 | stream_context_set_option($fp, 'ssl', 'verify_host', false);
481 | stream_context_set_option($fp, 'ssl', 'verify_peer_name', false);
482 | stream_context_set_option($fp, 'ssl', 'verify_peer', false);
483 | stream_context_set_option($fp, 'ssl', 'allow_self_signed', false);
484 | }
485 | stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
486 |
487 | $header = "GET "."/api/clientid/clientid/%s"." HTTP/1.1"."\r\n";
488 | $header .= "Host: %s"."\r\n";
489 | $header .= "\r\n";
490 | $buf = sprintf($header, $clientid, $host);
491 | $write = fputs($fp, $buf);
492 |
493 | $body = null;
494 | while (!feof($fp)) {
495 | $line = fgets($fp, 1024); //去除请求包的头只显示页面的返回数据
496 | if ($line == "\n" || $line == "\r\n") {
497 | $chunk_size = (integer) hexdec(fgets($fp, 1024));
498 | if ($chunk_size > 0) {
499 | $body = fread($fp, $chunk_size);
500 | break;
501 | }
502 | }
503 | }
504 |
505 | fclose($fp);
506 | $authData = json_decode($body, true);
507 |
508 | if ($authData['status'] != 200) {
509 | ConsoleOut('认证错误:' . $authData['msg'] . ' ErrorCode:' . $authData['status']);
510 | sleep(10);
511 | exit();
512 | }
513 | ConsoleOut('认证成功,正在连接服务器...');
514 | //设置映射隧道,支持多渠道[客户端id]
515 | ngrok_adds($authData['data']);
516 | $proto = explode(':', $authData['server']);
517 | return $proto;
518 | }
519 |
520 | //ngrok.cc 添加到渠道队列
521 | function ngrok_adds($Tunnel) {
522 | global $Tunnels;
523 | foreach ($Tunnel as $tunnelinfo) {
524 | if (isset($tunnelinfo['proto']['http'])) {
525 | $protocol = 'http';
526 | }
527 | if (isset($tunnelinfo['proto']['https'])) {
528 | $protocol = 'https';
529 | }
530 | if (isset($tunnelinfo['proto']['tcp'])) {
531 | $protocol = 'tcp';
532 | }
533 |
534 | $proto = explode(':', $tunnelinfo['proto'][$protocol]); //127.0.0.1:80 拆分成数组
535 | if ($proto[0] == '') {
536 | $proto[0] = '127.0.0.1';
537 | }
538 | if ($proto[1] == '' || $proto[1] == 0) {
539 | $proto[1] = 80;
540 | }
541 |
542 | $Tunnels[] = array(
543 | 'protocol' => $protocol,
544 | 'hostname' => $tunnelinfo['hostname'],
545 | 'subdomain' => $tunnelinfo['subdomain'],
546 | 'httpauth' => $tunnelinfo['httpauth'],
547 | 'rport' => $tunnelinfo['remoteport'],
548 | 'lhost' => $proto[0],
549 | 'lport' => $proto[1]
550 | );
551 | }
552 | }
553 |
554 | //注册退出执行函数
555 | function shutdown() {
556 | global $mainsocket;
557 | sendpack($mainsocket, 'close');
558 | fclose($mainsocket);
559 | }
560 |
561 | ?>
562 |
--------------------------------------------------------------------------------