09
2025
04
17:12:16

php ping ipv4 by socket

在php文档的笔记中搞了段php ping的代码php ping ipv4 by socket,顺便学习了ipv4 ICMP 报文和ipv4包结构,对ping的过程和php socket的使用有了初步的认识。本文记录下自己遇到的一些情况和代码分享

copy的代码遇到的问题

  1. ping 域名时,会把域名dns查找时间也计算进去

  2. 在本地测试ok,扔服务器上测试,失败率很高
    而直接用bash ping的话,丢包率很低,用这段代码的话,丢包率很高,且时高时低。实在是搞不明白原因是啥,只能去看看ip报文看看是代码哪写得不对了。
    在后续测试中发现:

  • 打印错误输出的话,经常是:

    • Not ICMP response

    • Bad identification number

    • Bad sequence number

  • 举个例子用后文的测试代码测试得到:

    Not ICMP response

    69 0 0 28 122 207 0 0 255 1 12 251 169 254 128 12 	# 发包的ip10 0 0 12 8 0 113 190 	# 类型 
    50 66 83 255
    • 报文

    • 错误信息

可以看到;

  1. 这里的包类型是8,是请求报文,还有169 254 128 12,是发包的ip,正常应该是我们ping的ip地址,也就是说我们收到了一个ping的请求包。

  2. 因为数据与会包的对不上,直接被算成失败,如果计算成丢包的话,丢包率会特别高。

  3. 而且下次ping的时候会收到上次ping的回包,得到了一次响应时间很低,但标识码和序列号错误的会包,此时由被视为丢包。

  4. 结果就是,一个错误的会包会导致后续的ping的雪崩式错误。

优化版本

改进的地方

  1. 域名解析时间单独计算

  2. 检测回包地址,丢弃非预期的回包

  3. 非预期的回包丢弃,在有效时间内继续等待回包

  4. 支持多次ping和统计

demo

require_once 'xping.php';
$back = xping::init()
    ->setDebug(false)
    ->setRet($ret)
    ->setError($error)
    ->doit('baidu.com',5,2000,1000,32);
var_dump(__FILE__.' line:'.__LINE__,$back,$ret,$error);exit;

out

//debug info
68.974018096924
63.398838043213
62.793016433716
62.896966934204
62.525987625122
string(64) "\xping.php line:*"// $backbool(true)
// $retarray(4) {
  ["host"]=>
  string(9) "baidu.com"
  ["dns_ms"]=>  float(0.0179290771484375)
  ["ip"]=>
  string(14) "220.181.38.251"
  ["ping_ret"]=>
  array(7) {
    ["avg"]=>    float(64.12)
    ["min"]=>    float(62.52598762512207)
    ["max"]=>    float(68.97401809692383)
    ["loss"]=>    float(0)
    ["sc"]=>
    int(5)
    ["rc"]=>
    int(5)
    ["str"]=>
    string(65) "send=5 recive=5 loss=0.00 %  min=62.53ms max=68.97ms avg=64.12ms "
  }
}
//$errorstring(8) "No Error"

$ret 部分说明

  • host
    ping domain

  • ip
    ping domain’s ip

  • dns_ms
    Domain name resolution time

  • ping_ret

    • rc
      recive count

    • sc
      send count

代码

Xxx-Bin/php-scoket-ping-Ipv4

测试代码

<?php/// start ping.inc.php ///$g_icmp_error = "No Error";// timeout in msfunction ping($host, $timeout){
    $port = 0;
    $datasize = 64;    global $g_icmp_error;
    $g_icmp_error = "No Error";
    $ident = [ord('J'), ord('C')];
    $seq = [rand(0, 255), rand(0, 255)];

    $packet = '';
    $packet .= chr(8); // type = 8 : request
    $packet .= chr(0); // code = 0

    $packet .= chr(0); // checksum init
    $packet .= chr(0); // checksum init

    $packet .= chr($ident[0]); // identifier
    $packet .= chr($ident[1]); // identifier

    $packet .= chr($seq[0]); // seq
    $packet .= chr($seq[1]); // seq

    for ($i = 0; $i < $datasize; $i++) {
        $packet .= chr(0);
    }

    $chk = icmpChecksum($packet);

    $packet[2] = $chk[0]; // checksum init
    $packet[3] = $chk[1]; // checksum init

    $sock = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
    $time_start = microtime(1);
    socket_sendto($sock, $packet, strlen($packet), 0, $host, $port);


    $read = [$sock];
    $write = null;
    $except = null;

    $select = socket_select($read, $write, $except, 0, $timeout * 1000);    if ($select === null) {
        $g_icmp_error = "Select Error";
        socket_close($sock);        return -1;
    } elseif ($select === 0) {
        $g_icmp_error = "Timeout";
        socket_close($sock);        return -1;
    }

    $recv = '';
    $time_stop = microtime(1);
    socket_recvfrom($sock, $recv, 65535, 0, $host, $port);
    $recv = unpack('C*', $recv);    if ($recv[10] !== 1) // ICMP proto = 1
    {
        $g_icmp_error = "Not ICMP packet";
        socket_close($sock);        return -1;
    }    if ($recv[21] !== 0) // ICMP response = 0
    {
        $g_icmp_error = "Not ICMP response ".implode(' ', $recv);
        socket_close($sock);        return -1;
    }    if ($ident[0] !== $recv[25] || $ident[1] !== $recv[26]) {
        $g_icmp_error = "Bad identification number ".implode(' ', $recv);
        socket_close($sock);        return -1;
    }    if ($seq[0] !== $recv[27] || $seq[1] !== $recv[28]) {
        $g_icmp_error = "Bad sequence number ".implode(' ', $recv);
        socket_close($sock);        return -1;
    }

    $ms = ($time_stop - $time_start) * 1000;    if ($ms < 0) {
        $g_icmp_error = "Response too long";
        $ms = -1;
    }

    socket_close($sock);    return $ms;
}function icmpChecksum($data){
    $bit = unpack('n*', $data);
    $sum = array_sum($bit);    if (strlen($data) % 2) {
        $temp = unpack('C*', $data[strlen($data) - 1]);
        $sum += $temp[1];
    }

    $sum = ($sum >> 16) + ($sum & 0xffff);
    $sum += ($sum >> 16);    return pack('n*', ~$sum);
}function getLastIcmpError(){    global $g_icmp_error;    return $g_icmp_error;
}/// end ping.inc.php ///// demoecho ping('1.0.0.3', 2000);echo $g_icmp_error.PHP_EOL;echo ping('1.0.0.3', 2000);echo $g_icmp_error.PHP_EOL;echo ping('1.0.0.4', 2000);echo $g_icmp_error.PHP_EOL;

后续

geerlingguy/Ping测试了这个项目种的socket ping,发现用socket->content后再socket_send和socket_read,不会出现收到请求包。难道是socket_recvfrom和socket_sendto有问题?还是socket_select呢?一脸懵逼。

再后续:
socket_create 确实也可以作为服务端接受数据,通过阅读文档得知,socket_recvfrom接收数据时,可以获取来源ip,此时可以直接进行判断,非预期的会包直接丢弃。check socket_recfrom host

相关

bjun.tech about ping




推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

本文链接:https://zblog.hqyman.cn/post/10279.html 非本站原创文章欢迎转载,原创文章需保留本站地址!

分享到:
打赏





休息一下~~


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

请先 登录 再评论,若不是会员请先 注册

您的IP地址是: