├── 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 | --------------------------------------------------------------------------------