NodeBlockedDetection.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. $this->checkNodes();
  28. }
  29. $jobEndTime = microtime(TRUE);
  30. $jobUsedTime = round(($jobEndTime-$jobStartTime), 4);
  31. Log::info("---【{$this->description}】完成---,耗时 {$jobUsedTime} 秒");
  32. }
  33. // 监测节点状态
  34. private function checkNodes()
  35. {
  36. $nodeList = SsNode::query()->where('is_transit', 0)->where('status', 1)->where('detectionType', '>', 0)->get();
  37. $sendText = FALSE;
  38. $message = "| 线路 | 协议 | 状态 |\r\n| ------ | ------ | ------ |\r\n";
  39. $additionalMessage = '';
  40. foreach($nodeList as $node){
  41. $info = FALSE;
  42. if($node->detectionType == 0){
  43. continue;
  44. }
  45. // 使用DDNS的node先通过gethostbyname获取ipv4地址
  46. if($node->is_ddns){
  47. $ip = gethostbyname($node->server);
  48. if(strcmp($ip, $node->server) != 0){
  49. $node->ip = $ip;
  50. }else{
  51. Log::warning("【节点阻断检测】检测".$node->server."时,IP获取失败".$ip." | ".$node->server);
  52. $this->notifyMaster("{$node->name}动态IP获取失败", "节点**{$node->name}**:** IP获取失败 **");
  53. }
  54. }
  55. if($node->detectionType != 1){
  56. $icmpCheck = $this->networkCheck($node->ip, TRUE, FALSE);
  57. if($icmpCheck != FALSE && $icmpCheck != "通讯正常"){
  58. $message .= "| ".$node->name." | ICMP | ".$icmpCheck." |\r\n";
  59. $sendText = TRUE;
  60. $info = TRUE;
  61. }
  62. }
  63. if($node->detectionType != 2){
  64. $tcpCheck = $this->networkCheck($node->ip, FALSE, $node->single? $node->port : FALSE);
  65. if($tcpCheck != FALSE && $tcpCheck != "通讯正常"){
  66. $message .= "| ".$node->name." | TCP | ".$tcpCheck." |\r\n";
  67. $sendText = TRUE;
  68. $info = TRUE;
  69. }
  70. }
  71. // 节点检测次数
  72. if($info){
  73. if(self::$systemConfig['numberOfWarningTimes']){
  74. // 已通知次数
  75. $cacheKey = 'numberOfWarningTimes'.$node->id;
  76. if(Cache::has($cacheKey)){
  77. $times = Cache::get($cacheKey);
  78. }else{
  79. // 键将保留12小时,多10分钟防意外
  80. Cache::put($cacheKey, 1, 83800);
  81. $times = 1;
  82. }
  83. if($times < self::$systemConfig['numberOfWarningTimes']){
  84. Cache::increment($cacheKey);
  85. }else{
  86. Cache::forget($cacheKey);
  87. SsNode::query()->where('id', $node->id)->update(['status' => 0]);
  88. $additionalMessage .= "\r\n**节点【{$node->name}】自动进入维护状态**\r\n";
  89. }
  90. }
  91. }
  92. }
  93. //只有在出现阻断线路时,才会发出警报
  94. if($sendText){
  95. $this->notifyMaster("节点阻断警告", "**阻断日志**: \r\n\r\n".$message.$additionalMessage);
  96. Log::info("阻断日志: \r\n".$message.$additionalMessage);
  97. }
  98. }
  99. /**
  100. * 通知管理员
  101. *
  102. * @param string $title 消息标题
  103. * @param string $content 消息内容
  104. *
  105. */
  106. private function notifyMaster($title, $content)
  107. {
  108. if(self::$systemConfig['webmaster_email']){
  109. $logId = Helpers::addEmailLog(self::$systemConfig['webmaster_email'], $title, $content);
  110. Mail::to(self::$systemConfig['webmaster_email'])->send(new nodeCrashWarning($logId));
  111. }
  112. ServerChan::send($title, $content);
  113. }
  114. /**
  115. * 用api.50network.com进行节点阻断检测
  116. *
  117. * @param string $ip 被检测的IP
  118. * @param boolean $type true 为ICMP,false 为tcp
  119. * @param int $port 检测端口
  120. *
  121. * @return bool|string
  122. */
  123. private function networkCheck($ip, $type, $port)
  124. {
  125. $url = 'https://api.50network.com/china-firewall/check/ip/'.($type? 'icmp/' : ($port? 'tcp_port/' : 'tcp_ack/')).$ip.($port? '/'.$port : '');
  126. $checkName = $type? 'ICMP' : 'TCP';
  127. try{
  128. $ret = json_decode(Curl::send($url), TRUE);
  129. if(!$ret){
  130. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口返回异常访问链接:".$url);
  131. return FALSE;
  132. }elseif(!$ret['success']){
  133. if($ret['error'] == "execute timeout (3s)"){
  134. sleep(10);
  135. return $this->networkCheck($ip, $type, $port);
  136. }else{
  137. Log::warning("【".$checkName."阻断检测】检测".$ip.($port? : '')."时,返回".json_encode($ret));
  138. }
  139. return FALSE;
  140. }
  141. } catch(Exception $e){
  142. Log::warning("【".$checkName."阻断检测】检测".$ip."时,接口请求超时".$e);
  143. return FALSE;
  144. }
  145. if($ret['firewall-enable'] && $ret['firewall-disable']){
  146. return "通讯正常"; // 正常
  147. }elseif($ret['firewall-enable'] && !$ret['firewall-disable']){
  148. return "海外阻断"; // 国外访问异常
  149. }elseif(!$ret['firewall-enable'] && $ret['firewall-disable']){
  150. return "国内阻断"; // 被墙
  151. }else{
  152. return "机器宕机"; // 服务器宕机
  153. }
  154. }
  155. }