NodeBlockedDetection.php 5.3 KB

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