Native PHP ping

PHP & JavaScript developer, working in Drupal.
Search for a command to run...

PHP & JavaScript developer, working in Drupal.
No comments yet. Be the first to comment.
A prelude to code conventions

First things first, it’s not always possible to get the current entity, some routes will have more than 1 entity, some wont have any, drush wont give a route. But most of the time, what you want when talking about the current entity is the entity bei...
Every time I need to make a pager ind Drupal I need to ask AI or / and do some searching to figure out.I don’t know why I can’t remember how, I just can’t, so this is a boilerplate for me (and anyone else who find it useful) for making paged SQL quer...
If you just want the code, here it is: https://github.com/BirkAndMe/hashnoderss.It should be straight forward, check the index.php file to get the gist of things. Intro A while back Hashnode released a new version of their public API, and one of the ...
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. -- MDN: Callback function Callbacks are used all over in JavaScript, a good ex...
To make a ping you'll need to know about the PHP Socket functions, ICMP protocol and Computing the Internet Checksum (don't let the RFCs scare you, the basics will be covered here).
The ICMP header starts after the regular IP header (which is handled by socket_connect). It's a pretty simple header of 8 bytes, followed by the message being sent.
╔═══════════════╦═══════════════╦══════════════════════════════╗
║ Type 8b ║ Code 8b ║ Checksum 16b ║
╠═══════════════╩═══════════════╬══════════════════════════════╣
║ Identifier 16b ║ Sequence number 16b ║
╠═══════════════════════════════╩══════════════════════════════╣
║ data (variable length) ║
╚══════════════════════════════════════════════════════════════╝
8, but there are a lot of other types, for other commands.0.0. The package is paired into 16-bits integers, and one's complement of the sum(explained later on).0.0. The original ping increments this on every ping.timeval struct to compute the round-trip.In this example, the package used is phping (when sending a ping the package will be the entire header and the data to send)
1) Divide the package into 16-bit pairs.
The package phping is the following in binary:
p: 0111 0000
h: 0110 1000
p: 0111 0000
i: 0110 1001
n: 0110 1110
g: 0110 0111
This is paired into 16-bit integers:
a: 0111 0000 0110 1000
b: 0111 0000 0110 1001
c: 0110 1110 0110 0111
2) Add all the pairs to get the sum.
Adding a, b and c give an integer larger than 16 bit:
a + b + c: 1 0100 1111 0011 1000
3) Sum 16-bit pairs of the sum again.
Reiterate the first 2 steps, until the sum is a single 16-bit integer.
Divide it:
a: 0000 0000 0000 0001
b: 0100 1111 0011 1000
Sum it:
a + b: 0100 1111 0011 1001
4) End with one's complement, to invert the integer.
One's complement is the same as a bitwise NOT operation.
~ 0100 1111 0011 1001: 1011 0000 1100 0110
There are a lot of tips and tricks, in the previously mentioned RFC 1071 for implementing this (and some examples). This is just one way of doing it.
function computeInternetChecksum($in) {
// Add an empty char (8-bit).
// This trick leverages the way unpack() works.
$in .= "\x0";
// The n* format splits up the data string into 16-bit pairs.
// It will unpack the string from the beginning, and only split
// whole pairs. So it will automatically leave out (or include) the
// odd byte added above.
$pairs = unpack('n*', $in);
// Sum the pairs.
$sum = array_sum($pairs);
// Add the hi 16 to the low 16 bits, ending in a single 16-bit int.
while ($sum >> 16)
$sum = ($sum >> 16) + ($sum & 0xffff);
// End with one's complement, to invert the integer.
// Note the ~ operator before packing the sum into a string again.
return pack('n', ~$sum);
}
Check phping gives the expected checksum:
$checksum = computeInternetChecksum('phping');
// Note that unpack() returns an array starting 1 (not 0).
echo decbin(unpack('n*', $checksum)[1]);
// Output (the same as manually calculated):
// 1011000011000110
If you're interested check out the original ping checksum implementation.
Another PHP implementation, that resembles the original ping implementation (and C implementation in the RFC) can be found in the socket_create() comments.
The package is set up following the ICMP header schema:
// Prepare the package.
$package = [
'type' => "\x08",
'code' => "\x00",
'checksum' => "\x00\x00",
'identifier' => "\x00\x00",
'seqNumber' => "\x00\x00",
'data' => 'phping',
];
// Compute the checksum, so it's ready to send.
$package['checksum'] = computeInternetChecksum(implode('', $package));
$rawPackage = implode('', $package);
Check the package is as expected:
// Unpack the package into an associated array.
$icmpHeaderFormat = 'Ctype/Ccode/nchecksum/nidentifier/nsequence';
// And show the binary values of each header part.
print_r(array_map('decbin', unpack($icmpHeaderFormat, $rawPackage)));
// Output:
// Array
// (
// [type] => 1000
// [code] => 0
// [checksum] => 1010100011000110
// [identifier] => 0
// [sequence] => 0
// )
Sending the package is done by using the PHP socket functions.
ICMP requests need raw network access, this causes permission issues, more on this later.
// Create the socket.
// AF_INIT is the IPv4 protocol.
// SOCK_RAW is needed to perform ICMP requests.
$socket = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
// Open up the connection to a host.
socket_connect($socket, 'google.com', null);
// Used to calculate the response time.
$time = microtime(true);
// Send the package to the target host.
socket_send($socket, $rawPackage, strlen($rawPackage), 0);
// Read the response.
if ($in = socket_read($socket, 1)) {
// Print the response time.
echo microtime(true) - $time . " seconds\n";
}
// Close the socket.
socket_close($socket);
This doesn't check the reply, and simply assumes any reply is valid. This is also why only 1 byte is read in the socket_read.
To check the reply, you would need to parse the input (including the IPv4 header) and verify the checksum.
Because of security risks, root / administrator access is needed to use SOCK_RAW (or the PHP executable needs CAP_NET_RAW capability).
So when testing the script you'll need to sudo the command (I believe the Windows equivalent would be run as administrator).
PHP will trigger the following warning (this may vary depending on OS), if it's not run with sufficient permissions:
PHP Warning: socket_create(): Unable to create socket [1]: Operation not permitted
First the script, ready for copy paste:
<?php
function computeInternetChecksum($in) {
// Add an empty char (8-bit).
// This trick leverages the way unpack() works.
$in .= "\x0";
// The n* format splits up the data string into 16-bit pairs.
// It will unpack the string from the beginning, and only split
// whole pairs. So it will automatically leave out (or include) the
// odd byte added above.
$pairs = unpack('n*', $in);
// Sum the pairs.
$sum = array_sum($pairs);
// Add the hi 16 to the low 16 bits, ending in a single 16-bit int.
while ($sum >> 16)
$sum = ($sum >> 16) + ($sum & 0xffff);
// End with one's complement, to invert the integer.
// Note the ~ operator before packing the sum into a string again.
return pack('n', ~$sum);
}
// Prepare the package.
$package = [
'type' => "\x08",
'code' => "\x00",
'checksum' => "\x00\x00",
'identifier' => "\x00\x00",
'seqNumber' => "\x00\x00",
'data' => 'phping',
];
// Compute the checksum, so it's ready to send.
$package['checksum'] = computeInternetChecksum(implode('', $package));
$rawPackage = implode('', $package);
// Create the socket.
// AF_INIT is the IPv4 protocol.
// SOCK_RAW is needed to perform ICMP requests.
$socket = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
// Open up the connection to a host.
socket_connect($socket, 'google.com', null);
// Used to calculate the response time.
$time = microtime(true);
// Send the package to the target host.
socket_send($socket, $rawPackage, strlen($rawPackage), 0);
// Read the response.
if ($in = socket_read($socket, 1)) {
// Print the response time.
echo microtime(true) - $time . " seconds\n";
}
// Close the socket.
socket_close($socket);
And the result:
$ sudo php ping.php
0.015023946762085 seconds
Because of the SOCK_RAW limitation, it's mostly an exercise in working with sockets, RFC documentations and binary string handling in PHP.
It might have its merits in a PHP CLI script, or if the PHP executable called by the webserver can get the CAP_NET_RAW using setcap.