Implementace ICMP Pingu v PHP

Stáhnout jako PDF autor: Johny, site, dne 10.1.2013


Potřeboval jsem naprogramovat poměrně sofistikovaný script, který bude kontrolovat dostupnost internetu. V případě výpadku analyzuje situaci a podle potřeby automaticky modifikuje routovací tabulku na routerech. Protože takovéto utilitky programuji nejčastěji v PHP5, tak jsem hledal řešení jak z PHP poslat ping. PHP ping st tak stal základním stavebním kamenem mého scriptu a protože jsem se nad ním pořádně zapotil, rozhodl jsem se tento kus kódu dát veřejně k dispozici. Třeba se někomu taky hodí.

Chci čistě PHP řešení

Protože jde o script, který by měl být maximálně spolehlivý, chci se vyhnout velké složitosti. Nechci jej řešit voláním externích „programů“, každé další volání by zbytečně zatěžuje systém. Nechci se spoléhat na návratové kódy. PHP ovšem nenabízí mnoho podpory, ale s tím, co umí to půjde.

Jak to tedy funguje?

Narazil jsem ale na celou řadu problémů. PHP nemá samo o sobě moc protokolu ICMP. Musel jsem tedy přistoupit k použití síťového RAW socketu a posílat tak „čistá“ data. Nevýhodou je, že pod linuxem má k raw socketu přístup jenom root (pod win taky, né?). Je to obrana před generováním „bordelu“ a „floodování“ sítě pro běžné uživatele.

Moje implementace je pouze velice základní.

  • vytvořím socket a nastavím timeout
  • připojím se k serveru
  • zašlu připravený paket \x08\x00\x7d\x4b\x00\x00\x00\x00PingHost
  • počkám, než mi přijde odpověď, nebo timeoutuji
  • pokud první „znak“ odpovědi je „E“, jedná se o ECHO REPLAY, změřím tedy čas a vrátím jej v sekundách
  • pokud přijde odpověď ale neobsahuje „E“, jde nejspíš o nějakou ICMP hlášku typu „Destination host unrechable“ nebo „Time exceeded“, tedy vrátím FALSE
  • pokud timeautuji, vrátím False

Všechny tři stavy mají i možnost „verbose“, tedy lidský výpis toho, co se opravdu stalo.

A zde je kód, možná se někomu hodí…

<?PHP

function php_ping($host, $timeout = 1, $verbose = false)
{
    /**
     * Return vždy vrací False ale pro příchod paketu ECHO_REPLAY 
     * jinak vrací čas odezvy PING. Tento script musí běžet 
     * pod ROOTem, jinak není možné přistupovat k RAW socketu!
     * @param  string  hostname or IP address
     * @param  int timeout
     * @param  bool verbose output
     * @return double
     */

    # vytvoří síťový socket pro RAW zápis, 
    # AF_INET => protokol IPV4
    # SOCK_RAW => poskytne možnost manuálně vytvořit libovolnou konstrukci paketu
    $socket = socket_create(AF_INET, SOCK_RAW, 1);

    # Nastaví Tmeout a připojí se k danému "hostu".
    socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array(
        'sec' => $timeout,
        'usec' => 0
    ));
    socket_connect($socket, $host, null);

    # Hlavičk ECHO REQUERS v hex \x08
    $header = "\x08\x00\x7d\x4b\x00\x00\x00\x00PingHost";

    $start = microtime(true);
    socket_send($socket, $header, strLen($header), 0);

    if ($result = socket_read($socket, 255)) {
        // Je odpoved Echo Replay? (ping odpovedel?)
        if (substr($result, 0, 1) == "E") {
            # Verbose? Zobraz víc info
            echo $verbose ? sprintf('%s bytes from %s time=%s ms',
            strLen($header), $host, ((100 * (microtime(true) - $start)))) : '';

            # Uzavře soket
            socket_close($socket);

            # Vrať čas
            return microtime(true) - $start;
        } else {
            //Odpoved přišla, ale né ECHO_REPLAY, tedy např. DEST UNRECHABLE atp.
            echo $verbose ? sprintf('ICMP Unrechable %s time=%s ms',
            $host, ((100 * (microtime(true) - $start)))) : '';

            socket_close($socket);
            return False;
        }
    } else {
        echo $verbose ? sprintf('ICMP Timeout %s time=%s ms',
        $host, ((100 * (microtime(true) - $start)))) : '';
        socket_close($socket);
        return False;
    }
}

# Příklad použití timeeout 1 sec, verbose výpis
php_ping('195.47.235.1', 1, True);

# Příklad pouhého zobrazení času pingu (případně "False")
echo php_ping('www.seznam.cz');

?>

Výstup

Pokud PHP spustím v shellu, vypadá výstup posledních dvou příkladů následovně:

/usr/bin/php5 ping.php
root@SVT:/usr/local/bin# ./ping.php
16 bytes from 195.47.235.1 time=0.9033203125 ms
0.0066969394683838
root@SVT:/usr/local/bin#

Samozřejmě že moje implementace je velice primitivní a obsahuje opravdu jen to nejnutnější, nicméně pro někoho toto může být dostatečné, případně může být návodem jak dál postupovat a funkci zlepšit.

Pokud by se našel někdo, kdo funkci vylepší, budu rád za zaslání – mohu můj zápisek vylepšit a rozšířit.


Podobné články:

Štítky tohoto článku:

 


 
Diskuze: Implementace ICMP Pingu v PHP
Vaše jméno (povinné)
Váš email (nebude zveřejněn, povinný)
WEB (bude zveřejněn, pište s http://)
Text vzkazu:
Kolik je 3×2? (ochrana proti spamu)
 
[CNW:Counter]