In try to replicate the outcome of the following command sequence using php:
netstat -rn | grep "^0.0.0.0 " | cut -d " " -f10
I did this using a PHP script:
#!/usr/bin/env php
<?php
/**
* Xdebug ip detector for docker
* Copyright (C) 2023 Dimitrios Desyllas
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
$matches = [];
preg_match("/^\w*\s(00000000)\s(\w*)/m",file_get_contents("/proc/net/route"),$matches);
// At regex each () is also matxched seperately. In my case I have 2 matched groups therefore the secodn match is my ip hex encoded
$ip = $matches[2];
$ip = str_split($ip,2);
$ip = array_reverse($ip);
$ip = array_reduce($ip,function($acc,$item){
return $acc.intval($item,16).".";
},"");
$ip = rtrim($ip,'.');
echo $ip;
Is this the way to parse the ip directly from /proc/net/route
?
This script is intended to be run inside a docker container that also ships with PHP and xdebug.
2 Answers 2
Reading the gateway IP
I'm not an expert of reading routing info, but:
- Linuxtopia's article on Linux Filesystem Hierarchy confirms that
/proc/net/route
contains the Kernel routing table. - The format assumed by the posted code matches what I get on recent Linux machine, and also on a pretty old Linux machine.
So I think your method is fine.
Another alternative to reading from /proc/net/route
would be to call the netstat
command as you mention in your post and parse its output.
Reading the gateway IP as a hex string
This functionality seems a good candidate to wrap into a function, having this "shape":
# Returns the gateway IP address as a hex string of reversed bytes,
# for example 0100A8C0, or null if not possible
function read_gateway_ip_hex_string() {
# ...
}
I find the preg_match
solution a bit hacky.
I find it more natural to consider the structure of the file as a table with columns, and get the 2nd column when the 1st column is 00000000:
function read_gateway_ip_hex_string() {
$fn = fopen("/proc/net/route", "r");
while(! feof($fn)) {
$line = fgets($fn);
$parts = preg_split("/\s+/", $line);
if ($parts[1] == "00000000") {
$ip_hex_string = $parts[2];
break;
}
}
fclose($fn);
return $ip_hex_string;
}
It's more verbose than the posted code, but I think it's easier to understand how it works.
Converting a hex string to dotted decimals
Similarly to getting the hex string, I think this is also a good opportunity to wrap this functionality into a reusable and testable function:
function ip_hex_string_to_dotted_decimals($ip_hex_string) {
$ip_bytes = array_reverse(
array_map(fn($s): int => intval($s, 16),
str_split($ip_hex_string, 2)));
return implode('.', $ip_bytes);
}
Instead of the array_reduce
+ rtrim
combo, this one maps array items and then joins them, which I find easier to understand.
Putting it together
From the building blocks in functions, the main part of the program becomes:
$ip_hex_string = read_gateway_ip_hex_string();
if (is_null($ip_hex_string)) {
# TODO: do something helpful!
} else {
$ip = ip_hex_string_to_dotted_decimals($ip_hex_string);
echo $ip;
}
-
\$\begingroup\$ well I wanted to use as a small script and not part of a major app in my case. Also docker container routes are small. \$\endgroup\$Dimitrios Desyllas– Dimitrios Desyllas2023年09月13日 20:44:09 +00:00Commented Sep 13, 2023 at 20:44
It is wise to ensure an array has a key before dereferencing it
After the call to preg_match()
the code accesses key 2
:
$ip = $matches[2];
While it may be improbable, it may be plausible that there is no value at index 2
and in that scenario a warning would be thrown:
Warning: Undefined array key 2
It is a good habit to ensure to guard against cases where an array doesn't have a key - perhaps with conditional logic, null coalescing operator or a null-safe operator. For example:
if (!isset($matches[2])) {
die('no encoded IP address found');
}
or something else that stops the code from attempting to access a non-existent array element.
Follow PHP Standards Recommendations
While there are no de jure rules about readability conventions and it is up to individuals/teams to decide their conventions, idiomatic PHP code often follows the PHP Standards Recommendations - e.g. PSR-12: Extended Coding Style.
$ip = str_split($ip,2);
This goes against PSR-12 section 4.5:
4.5 Method and Function Arguments
In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
Adding a space after the comma leads to idiomatic PHP code:
$ip = str_split($ip, 2);
Also - there are no spaces surrounding binary operators on some lines. For example:
return $acc.intval($item,16).".";
This goes against PSR-12 section 6.2:
6.2. Binary operators
All binary arithmetic, comparison, assignment, bitwise, logical, string, and type operators MUST be preceded and followed by at least one space:
Binary operators are typically separated with a space on both sides:
return $acc . intval($item,16) . ".";
Variable is over-written
There are five assignments to $ip
:
$ip = $matches[2]; $ip = str_split($ip,2); $ip = array_reverse($ip); $ip = array_reduce($ip,function($acc,$item){ return $acc.intval($item,16)."."; },""); $ip = rtrim($ip,'.');
Over-writing $ip
each time prevents re-using a value, which may not be a problem, though the meaning and type of the variable would change. For readability it would be better to use different names for each assignment. For example - one could do something like this:
// At regex each () is also matxched seperately. In my case I have 2 matched groups therefore the second match is my ip hex encoded
$encodedIP = $matches[2];
$parts = str_split($encodedIP, 2);
$reversed = array_reverse($parts);
$reconstructedIP = array_reduce($reversed, function($acc, $item){
return $acc . intval($item, 16) . ".";
}, '');
$ip = rtrim($reconstructedIP, '.');
Also - instead of using array_reduce()
and rtrim()
, one could use array_map()
and implode()
.
$ip = implode('.', array_map(function($item){
return intval($item, 16);
}, $reversed));
Or with an arrow function (PHP 7.4+):
$ip = implode('.', array_map(fn ($item) => intval($item, 16), $reversed));
-
\$\begingroup\$ So for the
$ip = $matches[2];
what approach would you reccomend? \$\endgroup\$Dimitrios Desyllas– Dimitrios Desyllas2023年09月14日 06:58:44 +00:00Commented Sep 14, 2023 at 6:58 -
\$\begingroup\$ Sorry for not getting back to you sooner... I've updated my answer \$\endgroup\$2024年06月26日 17:52:24 +00:00Commented Jun 26, 2024 at 17:52