研究下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 	# 发包的ip
    10 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. 支持多次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:*"
// $back
bool(true)
// $ret
array(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 "
  }
}
//$error
string(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 ms
function 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();
    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();
    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 ///

// demo
echo 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;

相关

bjun.tech about ping https://bjun.tech/blog/xphp?stid=71

原文

[update]php ping ipv4 by socket https://bjun.tech/blog/xphp/120

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值