ping.class.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. class Ping
  3. {
  4. private $host;
  5. private $ttl;
  6. private $timeout;
  7. private $port = 80;
  8. private $data = 'Ping';
  9. private $commandOutput;
  10. /**
  11. * Called when the Ping object is created.
  12. *
  13. * @param string $host
  14. * The host to be pinged.
  15. * @param int $ttl
  16. * Time-to-live (TTL) (You may get a 'Time to live exceeded' error if this
  17. * value is set too low. The TTL value indicates the scope or range in which
  18. * a packet may be forwarded. By convention:
  19. * - 0 = same host
  20. * - 1 = same subnet
  21. * - 32 = same site
  22. * - 64 = same region
  23. * - 128 = same continent
  24. * - 255 = unrestricted
  25. * @param int $timeout
  26. * Timeout (in seconds) used for ping and fsockopen().
  27. * @throws \Exception if the host is not set.
  28. */
  29. public function __construct($host, $ttl = 255, $timeout = 10)
  30. {
  31. if (!isset($host)) {
  32. throw new \Exception("Error: Host name not supplied.");
  33. }
  34. $this->host = $host;
  35. $this->ttl = $ttl;
  36. $this->timeout = $timeout;
  37. }
  38. /**
  39. * Set the ttl (in hops).
  40. *
  41. * @param int $ttl
  42. * TTL in hops.
  43. */
  44. public function setTtl($ttl)
  45. {
  46. $this->ttl = $ttl;
  47. }
  48. /**
  49. * Get the ttl.
  50. *
  51. * @return int
  52. * The current ttl for Ping.
  53. */
  54. public function getTtl()
  55. {
  56. return $this->ttl;
  57. }
  58. /**
  59. * Set the timeout.
  60. *
  61. * @param int $timeout
  62. * Time to wait in seconds.
  63. */
  64. public function setTimeout($timeout)
  65. {
  66. $this->timeout = $timeout;
  67. }
  68. /**
  69. * Get the timeout.
  70. *
  71. * @return int
  72. * Current timeout for Ping.
  73. */
  74. public function getTimeout()
  75. {
  76. return $this->timeout;
  77. }
  78. /**
  79. * Set the host.
  80. *
  81. * @param string $host
  82. * Host name or IP address.
  83. */
  84. public function setHost($host)
  85. {
  86. $this->host = $host;
  87. }
  88. /**
  89. * Get the host.
  90. *
  91. * @return string
  92. * The current hostname for Ping.
  93. */
  94. public function getHost()
  95. {
  96. return $this->host;
  97. }
  98. /**
  99. * Set the port (only used for fsockopen method).
  100. *
  101. * Since regular pings use ICMP and don't need to worry about the concept of
  102. * 'ports', this is only used for the fsockopen method, which pings servers by
  103. * checking port 80 (by default).
  104. *
  105. * @param int $port
  106. * Port to use for fsockopen ping (defaults to 80 if not set).
  107. */
  108. public function setPort($port)
  109. {
  110. $this->port = $port;
  111. }
  112. /**
  113. * Get the port (only used for fsockopen method).
  114. *
  115. * @return int
  116. * The port used by fsockopen pings.
  117. */
  118. public function getPort()
  119. {
  120. return $this->port;
  121. }
  122. /**
  123. * Return the command output when method=exec.
  124. * @return string
  125. */
  126. public function getCommandOutput()
  127. {
  128. return $this->commandOutput;
  129. }
  130. /**
  131. * Matches an IP on command output and returns.
  132. * @return string
  133. */
  134. public function getIpAddress()
  135. {
  136. $out = array();
  137. if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->commandOutput, $out)) {
  138. return $out[0];
  139. }
  140. return null;
  141. }
  142. /**
  143. * Ping a host.
  144. *
  145. * @param string $method
  146. * Method to use when pinging:
  147. * - exec (default): Pings through the system ping command. Fast and
  148. * robust, but a security risk if you pass through user-submitted data.
  149. * - fsockopen: Pings a server on port 80.
  150. * - socket: Creates a RAW network socket. Only usable in some
  151. * environments, as creating a SOCK_RAW socket requires root privileges.
  152. *
  153. * @throws InvalidArgumentException if $method is not supported.
  154. *
  155. * @return mixed
  156. * Latency as integer, in ms, if host is reachable or FALSE if host is down.
  157. */
  158. public function ping($method = 'exec')
  159. {
  160. $latency = false;
  161. switch ($method) {
  162. case 'exec':
  163. $latency = $this->pingExec();
  164. break;
  165. case 'fsockopen':
  166. $latency = $this->pingFsockopen();
  167. break;
  168. case 'socket':
  169. $latency = $this->pingSocket();
  170. break;
  171. default:
  172. throw new \InvalidArgumentException('Unsupported ping method.');
  173. }
  174. // Return the latency.
  175. return $latency;
  176. }
  177. /**
  178. * The exec method uses the possibly insecure exec() function, which passes
  179. * the input to the system. This is potentially VERY dangerous if you pass in
  180. * any user-submitted data. Be SURE you sanitize your inputs!
  181. *
  182. * @return int
  183. * Latency, in ms.
  184. */
  185. private function pingExec()
  186. {
  187. $latency = false;
  188. $ttl = escapeshellcmd($this->ttl);
  189. $timeout = escapeshellcmd($this->timeout);
  190. $host = escapeshellcmd($this->host);
  191. // Exec string for Windows-based systems.
  192. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  193. // -n = number of pings; -i = ttl; -w = timeout (in milliseconds).
  194. $exec_string = 'ping -n 1 -i ' . $ttl . ' -w ' . ($timeout * 1000) . ' ' . $host;
  195. } // Exec string for Darwin based systems (OS X).
  196. else if (strtoupper(PHP_OS) === 'DARWIN') {
  197. // -n = numeric output; -c = number of pings; -m = ttl; -t = timeout.
  198. $exec_string = 'ping -n -c 1 -m ' . $ttl . ' -t ' . $timeout . ' ' . $host;
  199. } // Exec string for other UNIX-based systems (Linux).
  200. else {
  201. // -n = numeric output; -c = number of pings; -t = ttl; -W = timeout
  202. $exec_string = 'ping -n -c 1 -t ' . $ttl . ' -W ' . $timeout . ' ' . $host . ' 2>&1';
  203. }
  204. exec($exec_string, $output, $return);
  205. // Strip empty lines and reorder the indexes from 0 (to make results more
  206. // uniform across OS versions).
  207. $this->commandOutput = implode('', $output);
  208. $output = array_values(array_filter($output));
  209. // If the result line in the output is not empty, parse it.
  210. if (!empty($output[1])) {
  211. // Search for a 'time' value in the result line.
  212. $response = preg_match("/time(?:=|<)(?<time>[\.0-9]+)(?:|\s)ms/", $output[1], $matches);
  213. // If there's a result and it's greater than 0, return the latency.
  214. if ($response > 0 && isset($matches['time'])) {
  215. $latency = round($matches['time'], 2);
  216. }
  217. }
  218. return $latency;
  219. }
  220. /**
  221. * The fsockopen method simply tries to reach the host on a port. This method
  222. * is often the fastest, but not necessarily the most reliable. Even if a host
  223. * doesn't respond, fsockopen may still make a connection.
  224. *
  225. * @return int
  226. * Latency, in ms.
  227. */
  228. private function pingFsockopen()
  229. {
  230. $start = microtime(true);
  231. // fsockopen prints a bunch of errors if a host is unreachable. Hide those
  232. // irrelevant errors and deal with the results instead.
  233. $fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
  234. if (!$fp) {
  235. $latency = false;
  236. } else {
  237. $latency = microtime(true) - $start;
  238. $latency = round($latency * 1000, 2);
  239. }
  240. return $latency;
  241. }
  242. /**
  243. * The socket method uses raw network packet data to try sending an ICMP ping
  244. * packet to a server, then measures the response time. Using this method
  245. * requires the script to be run with root privileges, though, so this method
  246. * only works reliably on Windows systems and on Linux servers where the
  247. * script is not being run as a web user.
  248. *
  249. * @return int
  250. * Latency, in ms.
  251. */
  252. private function pingSocket()
  253. {
  254. // Create a package.
  255. $type = "\x08";
  256. $code = "\x00";
  257. $checksum = "\x00\x00";
  258. $identifier = "\x00\x00";
  259. $seq_number = "\x00\x00";
  260. $package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
  261. // Calculate the checksum.
  262. $checksum = $this->calculateChecksum($package);
  263. // Finalize the package.
  264. $package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
  265. // Create a socket, connect to server, then read socket and calculate.
  266. if ($socket = socket_create(AF_INET, SOCK_RAW, 1)) {
  267. socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array(
  268. 'sec' => 10,
  269. 'usec' => 0,
  270. ));
  271. // Prevent errors from being printed when host is unreachable.
  272. @socket_connect($socket, $this->host, null);
  273. $start = microtime(true);
  274. // Send the package.
  275. @socket_send($socket, $package, strlen($package), 0);
  276. if (socket_read($socket, 255) !== false) {
  277. $latency = microtime(true) - $start;
  278. $latency = round($latency * 1000, 2);
  279. } else {
  280. $latency = false;
  281. }
  282. } else {
  283. $latency = false;
  284. }
  285. // Close the socket.
  286. socket_close($socket);
  287. return $latency;
  288. }
  289. /**
  290. * Calculate a checksum.
  291. *
  292. * @param string $data
  293. * Data for which checksum will be calculated.
  294. *
  295. * @return string
  296. * Binary string checksum of $data.
  297. */
  298. private function calculateChecksum($data)
  299. {
  300. if (strlen($data) % 2) {
  301. $data .= "\x00";
  302. }
  303. $bit = unpack('n*', $data);
  304. $sum = array_sum($bit);
  305. while ($sum >> 16) {
  306. $sum = ($sum >> 16) + ($sum & 0xffff);
  307. }
  308. return pack('n*', ~$sum);
  309. }
  310. }