5
\$\begingroup\$

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.

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Sep 13, 2023 at 19:31
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Reading the gateway IP

I'm not an expert of reading routing info, but:

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;
}
answered Sep 13, 2023 at 20:36
\$\endgroup\$
1
  • \$\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\$ Commented Sep 13, 2023 at 20:44
3
\$\begingroup\$

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));
answered Sep 13, 2023 at 21:05
\$\endgroup\$
2
  • \$\begingroup\$ So for the $ip = $matches[2]; what approach would you reccomend? \$\endgroup\$ Commented Sep 14, 2023 at 6:58
  • \$\begingroup\$ Sorry for not getting back to you sooner... I've updated my answer \$\endgroup\$ Commented Jun 26, 2024 at 17:52

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.