NodeBlockedDetection.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 $signature = 'nodeBlockedDetection';
  16. protected $description = '节点阻断检测';
  17. protected static $systemConfig;
  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. if($node->detectionType != 2){
  70. $tcpCheck = $this->networkCheck($node->ip, FALSE, $node->single? $node->port : FALSE);
  71. if($tcpCheck != FALSE){
  72. $text .= "| TCP | ".$tcpCheck."|\r\n";
  73. if($tcpCheck != '通讯正常'){
  74. $sendText = TRUE;
  75. }
  76. }
  77. }
  78. // 异常才发通知消息
  79. if($sendText){
  80. if(self::$systemConfig['numberOfWarningTimes']){
  81. // 已通知次数
  82. $cacheKey = 'numberOfWarningTimes'.$node->id;
  83. if(Cache::has($cacheKey)){
  84. $times = Cache::get($cacheKey);
  85. }else{
  86. Cache::put($cacheKey, 1, 725); // 最多设置提醒12次,12*60=720分钟缓存时效,多5分钟防止异常
  87. $times = 1;
  88. }
  89. if($times < self::$systemConfig['numberOfWarningTimes']){
  90. Cache::increment($cacheKey);
  91. }else{
  92. Cache::forget($cacheKey);
  93. SsNode::query()->where('id', $node->id)->update(['status' => 0]);
  94. $text .= "\r\n**节点自动进入维护状态**\r\n";
  95. }
  96. }
  97. $this->notifyMaster($title, "**{$node->name} - 【{$node->ip}】**: \r\n\r\n".$text, $node->name, $node->server);
  98. Log::info("【节点阻断检测】{$node->name} - 【{$node->ip}】: \r\n".$text);
  99. }
  100. }
  101. // 随机生成下次检测时间
  102. $nextCheckTime = time()+3600;
  103. Cache::put('LastCheckTime', $nextCheckTime, 60);
  104. }
  105. /**
  106. * 用api.50network.com进行节点阻断检测
  107. *
  108. * @param string $ip 被检测的IP
  109. * @param boolean $type true 为ICMP,false 为tcp
  110. * @param int $port 检测端口
  111. *
  112. * @return bool|string
  113. */
  114. private function networkCheck($ip, $type, $port)
  115. {
  116. $url = 'https://api.50network.com/china-firewall/check/ip/'.($type? 'icmp/' : ($port? 'tcp_port/' : 'tcp_ack/')).$ip.($port? '/'.$port : '');
  117. $checkName = $type? 'ICMP' : 'TCP';
  118. try{
  119. $ret = json_decode(Curl::send($url), TRUE);
  120. if(!$ret){
  121. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口返回异常访问链接:".$url);
  122. return FALSE;
  123. }elseif(!$ret['success']){
  124. Log::warning("【".$checkName."阻断检测】检测".$ip."时,返回".json_encode($ret));
  125. return FALSE;
  126. }
  127. } catch(Exception $e){
  128. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口请求超时".$e);
  129. return FALSE;
  130. }
  131. if($ret['firewall-enable'] && $ret['firewall-disable']){
  132. return '通讯正常'; // 正常
  133. }elseif($ret['firewall-enable'] && !$ret['firewall-disable']){
  134. return '海外阻断'; // 国外访问异常
  135. }elseif(!$ret['firewall-enable'] && $ret['firewall-disable']){
  136. return '国内阻断'; // 被墙
  137. }else{
  138. return '机器宕机'; // 服务器宕机
  139. }
  140. }
  141. /**
  142. * 通知管理员
  143. *
  144. * @param string $title 消息标题
  145. * @param string $content 消息内容
  146. * @param string $nodeName 节点名称
  147. * @param string $nodeServer 节点域名
  148. *
  149. */
  150. private function notifyMaster($title, $content, $nodeName, $nodeServer)
  151. {
  152. if(self::$systemConfig['webmaster_email']){
  153. $logId = Helpers::addEmailLog(self::$systemConfig['webmaster_email'], $title, $content);
  154. Mail::to(self::$systemConfig['webmaster_email'])->send(new nodeCrashWarning($logId, $nodeName, $nodeServer));
  155. }
  156. ServerChan::send($title, $content);
  157. }
  158. }