NodeBlockedDetection.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Components\Curl;
  4. use App\Components\Helpers;
  5. use App\Components\ServerChan;
  6. use App\Http\Models\SsNode;
  7. use App\Mail\nodeCrashWarning;
  8. use Cache;
  9. use Exception;
  10. use Illuminate\Console\Command;
  11. use Log;
  12. use Mail;
  13. class NodeBlockedDetection extends Command
  14. {
  15. protected static $systemConfig;
  16. protected $signature = 'nodeBlockedDetection';
  17. protected $description = '节点阻断检测';
  18. public function __construct()
  19. {
  20. parent::__construct();
  21. self::$systemConfig = Helpers::systemConfig();
  22. }
  23. public function handle()
  24. {
  25. $jobStartTime = microtime(TRUE);
  26. if(self::$systemConfig['nodes_detection']){
  27. if(!Cache::has('LastCheckTime')){
  28. $this->checkNodes();
  29. }elseif(Cache::get('LastCheckTime') <= time()){
  30. $this->checkNodes();
  31. }else{
  32. Log::info('下次节点阻断检测时间:'.date('Y-m-d H:i:s', Cache::get('LastCheckTime')));
  33. }
  34. }
  35. $jobEndTime = microtime(TRUE);
  36. $jobUsedTime = round(($jobEndTime-$jobStartTime), 4);
  37. Log::info("---【{$this->description}】完成---,耗时 {$jobUsedTime} 秒");
  38. }
  39. // 监测节点状态
  40. private function checkNodes()
  41. {
  42. $nodeList = SsNode::query()->where('is_transit', 0)->where('status', 1)->where('detectionType', '>', 0)->get();
  43. foreach($nodeList as $node){
  44. $title = "【{$node->name}】阻断警告";
  45. if($node->detectionType == 0){
  46. continue;
  47. }
  48. // 使用DDNS的node先通过gethostbyname获取ipv4地址
  49. if($node->is_ddns){
  50. $ip = gethostbyname($node->server);
  51. if(strcmp($ip, $node->server) != 0){
  52. $node->ip = $ip;
  53. }else{
  54. Log::warning("【节点阻断检测】检测".$node->server."时,IP获取失败".$ip." | ".$node->server);
  55. $this->notifyMaster($title, "节点**{$node->name}**:** IP获取失败 **", $node->name, $node->server);
  56. }
  57. }
  58. $sendText = FALSE;
  59. $text = "| 协议 | 状态 |\r\n| :------ | :------ |\r\n";
  60. if($node->detectionType != 1){
  61. $icmpCheck = $this->networkCheck($node->ip, TRUE, FALSE);
  62. if($icmpCheck != FALSE){
  63. $text .= "| ICMP | ".$icmpCheck."|\r\n";
  64. if($icmpCheck != '通讯正常'){
  65. $sendText = TRUE;
  66. }
  67. }
  68. }
  69. sleep(3);
  70. if($node->detectionType != 2){
  71. $tcpCheck = $this->networkCheck($node->ip, FALSE, $node->single? $node->port : FALSE);
  72. if($tcpCheck != FALSE){
  73. $text .= "| TCP | ".$tcpCheck."|\r\n";
  74. if($tcpCheck != '通讯正常'){
  75. $sendText = TRUE;
  76. }
  77. }
  78. }
  79. // 异常才发通知消息
  80. if($sendText){
  81. if(self::$systemConfig['numberOfWarningTimes']){
  82. // 已通知次数
  83. $cacheKey = 'numberOfWarningTimes'.$node->id;
  84. if(Cache::has($cacheKey)){
  85. $times = Cache::get($cacheKey);
  86. }else{
  87. Cache::put($cacheKey, 1, 43200); // 最多设置提醒12次,每次1小时间隔
  88. $times = 1;
  89. }
  90. if($times < self::$systemConfig['numberOfWarningTimes']){
  91. Cache::increment($cacheKey);
  92. }else{
  93. Cache::forget($cacheKey);
  94. SsNode::query()->where('id', $node->id)->update(['status' => 0]);
  95. $text .= "\r\n**节点自动进入维护状态**\r\n";
  96. }
  97. }
  98. $this->notifyMaster($title, "**{$node->name} - 【{$node->ip}】**: \r\n\r\n".$text, $node->name, $node->server);
  99. Log::info("【节点阻断检测】{$node->name} - 【{$node->ip}】: \r\n".$text);
  100. }
  101. sleep(3);
  102. }
  103. // 随机生成下次检测时间
  104. $nextCheckTime = time()+3600;
  105. Cache::put('LastCheckTime', $nextCheckTime, 3600);
  106. }
  107. /**
  108. * 通知管理员
  109. *
  110. * @param string $title 消息标题
  111. * @param string $content 消息内容
  112. * @param string $nodeName 节点名称
  113. * @param string $nodeServer 节点域名
  114. *
  115. */
  116. private function notifyMaster($title, $content, $nodeName, $nodeServer)
  117. {
  118. if(self::$systemConfig['webmaster_email']){
  119. $logId = Helpers::addEmailLog(self::$systemConfig['webmaster_email'], $title, $content);
  120. Mail::to(self::$systemConfig['webmaster_email'])->send(new nodeCrashWarning($logId, $nodeName, $nodeServer));
  121. }
  122. ServerChan::send($title, $content);
  123. }
  124. /**
  125. * 用api.50network.com进行节点阻断检测
  126. *
  127. * @param string $ip 被检测的IP
  128. * @param boolean $type true 为ICMP,false 为tcp
  129. * @param int $port 检测端口
  130. *
  131. * @return bool|string
  132. */
  133. private function networkCheck($ip, $type, $port)
  134. {
  135. $url = 'https://api.50network.com/china-firewall/check/ip/'.($type? 'icmp/' : ($port? 'tcp_port/' : 'tcp_ack/')).$ip.($port? '/'.$port : '');
  136. $checkName = $type? 'ICMP' : 'TCP';
  137. try{
  138. $ret = json_decode(Curl::send($url), TRUE);
  139. if(!$ret){
  140. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口返回异常访问链接:".$url);
  141. return FALSE;
  142. }elseif(!$ret['success']){
  143. Log::warning("【".$checkName."阻断检测】检测".$ip."时,返回".json_encode($ret));
  144. return FALSE;
  145. }
  146. } catch(Exception $e){
  147. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口请求超时".$e);
  148. return FALSE;
  149. }
  150. if($ret['firewall-enable'] && $ret['firewall-disable']){
  151. return '通讯正常'; // 正常
  152. }elseif($ret['firewall-enable'] && !$ret['firewall-disable']){
  153. return '海外阻断'; // 国外访问异常
  154. }elseif(!$ret['firewall-enable'] && $ret['firewall-disable']){
  155. return '国内阻断'; // 被墙
  156. }else{
  157. return '机器宕机'; // 服务器宕机
  158. }
  159. }
  160. }