Looking for correct PHP socket error constants - php

I have an odd problem that's likely due to my inexperience with socket programming.
I'm currently using the raw error code numbers I'm getting from socket_last_error() as I see I need to handle them. This is getting unwieldy.
I'd like to use predefined constants (either my own or builtin) to refer to the different types of socket errors/conditions I need to deal with.
The error tables I've found all use conflicting values.
At http://php.net/manual/en/sockets.constants.php (in a comment that lists the raw numbers), I see things like (excerpted):
SOCKET_EINPROGRESS 10036
SOCKET_EALREADY 10037
SOCKET_ENETUNREACH 10051
The one comment at http://php.net/socket_last_error contains a list of what are apparently the "standard C defines" for socket errors (?):
define('EINPROGRESS', 115); /* Operation now in progress */
define('EALREADY', 114); /* Operation already in progress */
define('ENETUNREACH', 101); /* Network is unreachable */
The errno.h file on my own system (hiding in /usr/include/asm-generic/) seems to support this:
#define EINPROGRESS 115 /* Operation now in progress */
#define EALREADY 114 /* Operation already in progress */
#define ENETUNREACH 101 /* Network is unreachable */
However those "standard definitions" seem to be subject to change depending on what OS you're on: BSD4.4's errno.h has things like
#define EINPROGRESS 36 /* Operation now in progress */
#define EALREADY 37 /* Operation already in progress */
#define ENETUNREACH 51 /* Network is unreachable */
Now we know what the socket_* functions were inspired by though!
Finally, I find what seems to be a hint of an explanation hiding in the VirtualBox source code:
#ifndef EALREADY
# if defined(RT_ERRNO_OS_BSD)
# define EALREADY (37)
# elif defined(RT_OS_LINUX)
# define EALREADY (114)
# else
# define EALREADY (149)
# endif
#endif
With all of this taken into account...
socket_last_error() returns an errno of 101 when Network is unreachable, as opposed to 51 or 10051. So this function appears to be in apparent violation of the socket library's officially-supplied constants, and seems to be using Linux's error codes instead.
([EDIT after adding my answer]: The 101 stated above was obtained on Linux.)
So now that I seem to be in Undocumented and/or Seemingly Undefined Behavior land... what do I do now? I'm on Linux right now; do these values ever change?
Is there some way I can use the offical SOCKET_* constants? I certainly wouldn't mind doing so.

Sounds like you've been researching this hard -- and also that the platform-specific stuff might be a nuisance. Let me suggest this code which will ostensibly fetch all defined constants grouped together as "sockets" constants:
$consts = get_defined_constants(TRUE);
$socket_constants = $consts["sockets"];
foreach($socket_constants as $key => $value){
echo $key . '=' . $value . "\n";
}
From that, I was able to construct this function which you may find useful on any platform for finding the names of socket constants which might match
function get_socket_constant_names($to_check) {
$consts = get_defined_constants(TRUE);
$socket_constants = $consts["sockets"];
$matches = array();
foreach($socket_constants as $key => $value){
if ($value == $to_check) {
$matches[] = $key;
}
}
return $matches;
}
This:
var_dump(get_socket_constant_names(101));
Yields this:
array(1) {
[0] =>
string(18) "SOCKET_ENETUNREACH"
}

I just did some digging.
WARNING. PHP returns OS-specific socket error codes.
I do not know how to retrieve error codes compatible with the socket_* constants, so you have to detect the OS :'(
See proof after source code.
The following are heavily elided to aid focus.
From https://github.com/php/php-src/blob/master/ext/sockets/sockets.c:
PHP_FUNCTION(socket_connect) {
// will set errno
retval = connect(php_sock->bsd_socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in));
if (retval != 0) {
// call macro defined in php_sockets.h with errno value
PHP_SOCKET_ERROR(php_sock, "unable to connect", errno);
RETURN_FALSE;
}
RETURN_TRUE;
}
PHP_FUNCTION(socket_last_error) {
// check for argument
if (arg1) {
if ((php_sock = (php_socket *)zend_fetch_resource(Z_RES_P(arg1), le_socket_name, le_socket)) == NULL) {
RETURN_FALSE;
}
// return errno from passed socket resource
RETVAL_LONG(php_sock->error);
} else {
// return errno from last globally set value
RETVAL_LONG(SOCKETS_G(last_error));
}
}
From https://github.com/php/php-src/blob/master/ext/sockets/php_sockets.h:
#define PHP_SOCKET_ERROR(socket, msg, errn) \
// store the value for this socket resource
(socket)->error = _err; \
// store the value globally
SOCKETS_G(last_error) = _err
Proof:
<?php
$s = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($s);
socket_connect($s, "google.com", "80");
var_dump(socket_last_error());
var_dump(socket_strerror(socket_last_error()));
Observe the difference in socket_last_error():
linux$ php asdf.php
int(115)
string(25) "Operation now in progress"
freebsd$ php asdf.php
int(36)
string(25) "Operation now in progress"
TL;DR: There's no defined list of constants inside PHP (that I can see how to use). You must supply your own OS-correct list of constants.

Related

In PHP, how can I detect that input vars were truncated due to max_input_vars being exceeded?

I know that an E_WARNING is generated by PHP
PHP Warning: Unknown: Input variables exceeded 1000
But how can I detect this in my script?
A "close enough" method would be to check if( count($_POST, COUNT_RECURSIVE) == ini_get("max_input_vars"))
This will cause a false positive if the number of POST vars happens to be exactly on the limit, but considering the default limit is 1000 it's unlikely to ever be a concern.
count($_POST, COUNT_RECURSIVE) is not accurate because it counts all nodes in the array tree whereas input_vars are only the terminal nodes. For example, $_POST['a']['b'] = 'c' has 1 input_var but using COUNT_RECURSIVE will return 3.
php://input cannot be used with enctype="multipart/form-data". http://php.net/manual/en/wrappers.php.php
Since this issue only arises with PHP >= 5.3.9, we can use anonymous functions. The following recursively counts the terminals in an array.
function count_terminals($a) {
return is_array($a)
? array_reduce($a, function($carry, $item) {return $carry + count_terminals($item);}, 0)
: 1;
}
What works for me is this. Firstly, I put this at the top of my script/handler/front controller. This is where the error will be saved (or $e0 will be null, which is OK).
$e0 = error_get_last();
Then I run a bunch of other processing, bootstrapping my application, registering plugins, establishing sessions, checking database state - lots of things - that I can accomplish regardless of exceeding this condition.. Then I check this $e0 state. If it's not null, we have an error so I bail out (assume that App is a big class with lots of your magic in it)
if (null != $e0) {
ob_end_clean(); // Purge the outputted Warning
App::bail($e0); // Spew the warning in a friendly way
}
Tweak and tune error handlers for your own state.
Registering an error handler won't catch this condition because it exists before your error handler is registered.
Checking input var count to equal the maximum is not reliable.
The above $e0 will be an array, with type => 8, and line => 0; the message will explicitly mention input_vars so you could regex match to create a very narrow condition and ensure positive identification of the specific case.
Also note, according to the PHP specs this is a Warning not an Error.
function checkMaxInputVars()
{
$max_input_vars = ini_get('max_input_vars');
# Value of the configuration option as a string, or an empty string for null values, or FALSE if the configuration option doesn't exist
if($max_input_vars == FALSE)
return FALSE;
$php_input = substr_count(file_get_contents('php://input'), '&');
$post = count($_POST, COUNT_RECURSIVE);
echo $php_input, $post, $max_input_vars;
return $php_input > $post;
}
echo checkMaxInputVars() ? 'POST has been truncated.': 'POST is not truncated.';
Call error_get_last() as soon as possible in your script (before you have a chance to cause errors, as they will obscure this one.) In my testing, the max_input_vars warning will be there if applicable.
Here is my test script with max_input_vars set to 100:
<?php
if (($error = error_get_last()) !== null) {
echo 'got error:';
var_dump($error);
return;
}
unset($error);
if (isset($_POST['0'])) {
echo 'Got ',count($_POST),' vars';
return;
}
?>
<form method="post">
<?php
for ($i = 0; $i < 200; $i++) {
echo '<input name="',$i,'" value="foo" type="hidden">';
}
?>
<input type="submit">
</form>
Output when var limit is hit:
got error:
array
'type' => int 2
'message' => string 'Unknown: Input variables exceeded 100. To increase the limit change max_input_vars in php.ini.' (length=94)
'file' => string 'Unknown' (length=7)
'line' => int 0
Tested on Ubuntu with PHP 5.3.10 and Apache 2.2.22.
I would be hesitant to check explicitly for this error string, for stability (they could change it) and general PHP good practice. I prefer to turn all PHP errors into exceptions, like this (separate subclasses may be overkill, but I like this example because it allows # error suppression.) It would be a little different coming from error_get_last() but should be pretty easy to adapt.
I don't know if there are other pre-execution errors that could get caught by this method.
What about something like that:
$num_vars = count( explode( '###', http_build_query($array, '', '###') ) );
You can repeat it both for $_POST, $_GET, $_COOKIE, whatever.
Still cant be considered 100% accurate, but I guess it get pretty close to it.

issue with stream_select() in PHP

I am using stream_select() but it returns 0 number of descriptors after few seconds and my function while there is still data to be read.
An unusual thing though is that if you set the time out as 0 then I always get the number of descriptors as zero.
$num = stream_select($read, $w, $e, 0);
stream_select() must be used in a loop
The stream_select() function basically just polls the stream selectors you provided in the first three arguments, which means it will wait until one of the following events occur:
some data arrives
or reaches timeout (set with $tv_sec and $tv_usec) without getting any data.
So recieving 0 as a return value is perfectly normal, it means there was no new data in the current polling cycle.
I'd suggest to put the function in a loop something like this:
$EOF = false;
do {
$tmp = null;
$ready = stream_select($read, $write, $excl, 0, 50000);
if ($ready === false ) {
// something went wrong!!
break;
} elseif ($ready > 0) {
foreach($read as $r) {
$tmp .= stream_get_contents($r);
if (feof($r)) $EOF = true;
}
if (!empty($tmp)) {
//
// DO SOMETHING WITH DATA
//
continue;
}
} else {
// No data in the current cycle
}
} while(!$EOF);
Please note that in this example, the script totally ignores everything aside from the input stream. Also, the third section of the "if" statement is completely optional.
Does it return the number 0 or a FALSE boolean? FALSE means there was some error but zero could be just because of timeout or nothing interesting has happen with the streams and you should do a new select etc.
I would guess this could happen with a zero timeout as it will check and return immediately. Also if you read the PHP manual about stream-select you will see this warning about using zero timeout:
Using a timeout value of 0 allows you to instantaneously poll the status of the streams, however, it is NOT a good idea to use a 0 timeout value in a loop as it will cause your script to consume too much CPU time.
If this is a TCP stream and you want to check for connection close you should check the return value from fread etc to determine if the other peer has closed the conneciton. About the read streams array argument:
The streams listed in the read array will be watched to see if characters become available for reading (more precisely, to see if a read will not block - in particular, a stream resource is also ready on end-of-file, in which case an fread() will return a zero length string).
http://www.php.net/stream_select
Due to a limitation in the current Zend Engine it is not possible to
pass a constant modifier like NULL directly as a parameter to a
function which expects this parameter to be passed by reference.
Instead use a temporary variable or an expression with the leftmost
member being a temporary variable:
<?php $e = NULL; stream_select($r, $w, $e, 0); ?>
I have a similar issue which is caused by the underlying socket timeout.
Eg. I create some streams
$streams = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
Then fork, and use a block such as the following
stream_set_blocking($pipes[1], 1);
stream_set_blocking($pipes[2], 1);
$pipesToRead = array($pipes[1], $pipes[2]);
while (!feof($pipesToRead[0]) || !feof($pipesToRead[1])) {
$reads = $pipesToRead;
$writes = null;
$excepts = $pipesToRead;
$tSec = null;
stream_select($reads, $writes, $excepts, $tSec);
// while it's generating any kind of output, duplicate it wherever it
// needs to go
foreach ($reads as &$read) {
$chunk = fread($read, 8192);
foreach ($streams as &$stream)
fwrite($stream, $chunk);
}
}
Glossing over what other things might be wrong there, my $tSec argument to stream_select is ignored, and the "stream" will timeout after 60 seconds of inactivity and produce an EOF.
If I add the following after creating the streams
stream_set_timeout($streams[0], 999);
stream_set_timeout($streams[1], 999);
Then I get the result I desire, even if there's no activity on the underlying stream for longer than 60 seconds.
I feel that this might be a bug, because I don't want that EOF after 60 seconds of inactivity on the underlying stream, and I don't want to plug in some arbitrarily large value to avoid hitting the timeout if my processes are idle for some time.
In addition, even if the 60 second timeout remains, I think it should just timeout on my stream_select() call and my loop should be able to continue.

Socket_read returning '1' ..?

I have recently started practicing with sockets on PHP and got an issue for which I find no documentation. Similar cases I've seen in C++, but not a clear answer to this. The code:
do {
$input = socket_read($client, 12,PHP_BINARY_READ);
echo $input;
} while(TRUE);
Is supposed to block on the socket (code for creation, bind, etc not included) and get either 12 bytes or whatever information is available from the other side.
Oddly I just get a '1' in the variable $input if 12 bytes are read. If I send from the client side more than 12 bytes then I receive '1[REST_OF_DATA]' in the value of $input.
Any idea why this is happening?
If I change this to more data and to PHP_NORMAL_READ then I correctly receive the data.
PHP manual online does not say anything abou socket_read returning '1'..
**EDIT: Ok thanks for yout early answers :). I am saving to a file and reading (not echoing to browser) expecting any character. I think I may have just discovered something that could be good if someone with knowledge of C++ sockets can verify. Anyways, my read code actually was this (not what I posted above):
do {
$input = ($seq_id == 0) ? socket_read($client, 12,PHP_BINARY_READ) : socket_read($client,1024,PHP_BINARY_READ);
echo $input;
} while(TRUE);
I was expecting 12 bytes at the first read, then chunks of 1024, reason for that condition check. The weird '1' comes from this. If I replace that with the line I posted above the data is read normally. In fact, even reading like this:
$input = ($seq_ID == 0) ? socket_read($client,12,PHP_BINARY_READ) : socket_read($client, 12,PHP_BINARY_READ);
Results in : 1st read = '1' 2nd read = correct data, 3rd read = correct data..
The 12 you specify is the maximum length to read, so 12 can mean a return string of a size from 0-12 characters (binary string in PHP, 1 char = 1 byte).
Additionally as that can be binary, I suggest you use var_dump and a hexdump of the return string value to actually find out how many bytes were returned, echo might hide some control characters, your browser might hide whitespace.
For the comments above, yes $seq_id should increment. I just wanted to shorten the code. So now, the answer is not important for me anymore but remains an enigma, after upgrading my Ubuntu version this month I have been unable to replicate the error with the same script:
<?php
set_time_limit(0);
$address = "192.168.1.1";
$port = 3320;
$server_users = 3;
$mysock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create socket\n");
$bind_result = socket_bind($mysock,$address, $port) or die("Could not bind to address\n");
$listen_result = socket_listen($mysock, $server_users) or die("Could not set up socket listener\n");
$client = socket_accept($mysock) or die("Could not accept the connection to socket\n");
$seq_id =0;
do {
$input = ($seq_id == 0) ? socket_read($client, 12,PHP_BINARY_READ) : socket_read($client,1024,PHP_BINARY_READ);
echo $input;
} while(TRUE);
?>
I execute it in terminal:
$php -q myscript.php
And test it using netcat:
$netcat 192.168.1.1 3320
Note that the question is about socket_read returning 1 as it results, which is not documented anywhere

Throwing errors from its "correct" source

I hope the title isn't too confusing, I'll try to explain better below.
Suppose I have a function in a separate file, functions.php:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
And another file that calls it:
include "functions.php";
echo divide(10, 0);
My error is
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\functions.php on line 5
My question is, how do I make that error instead point to the location of the error in the main code, so I instead get:
Fatal error: Cannot divide by 0 in
C:\Users\Derek\Desktop\projects\main.php on line 3
The particular reason I want this is because I have a function called load_class that simply finds a PHP file and instantiates the object inside, but if given an incorrect file name, it reports an error from inside load_class, which is technically true, but it's not particularly helpful if I don't remember where I called load_class in the first place. I would like the error to point to the file that called load_class incorrectly.
Also, I would like to write a function error() (something like below) that when given a message as a parameter would throw more "meaningful" error messages, but when done that way, the error always says it comes from error(), not from where the error actually came from!
For example, in an error.php:
/**
* error()
*
* Throws an error of a certain type
*
* #param string $type The type of error. "Fatal", "warning," or "notice"
* #param string $message A description of the error
* #return void
*/
function error($type, $message) {
switch (strtolower($type)) {
case 'fatal':
trigger_error($message, E_USER_ERROR);
break;
case 'notice':
trigger_error($message, E_USER_NOTICE);
default:
trigger_error($message, E_USER_WARNING);
break;
}
}
And in an index.php
error("fatal", "A sample warning!");
My error given is:
Fatal error: A sample warning! in
C:\Users\Derek\Desktop\projects\synthesis\sys\Error.php on line 45
But the error didn't occur in error.php, it happened in index.php! How can I make it show where it really came from?
The debug_backtrace function allows you to obtain the stacktrace as an array. You can pick the original location from there.
Next to that you need to slip into the error message to make this look-alike. Example:
function divide($num1, $num2) {
if ($num1 == 0 || $num2 == 0) {
trigger_error_original("Cannot divide by 0", E_USER_ERROR);
} else {
return ($num1 / $num2);
}
}
function trigger_error_original($message, $type) {
$trace = debug_backtrace(FALSE);
list($location) = array_slice($trace, 1, 1) + array('file' => 'unknown', 'line' => 'unknown');
$message .= sprintf(" in %s on line %d\nTriggered", $location['file'], $location['line']);
trigger_error($message, $type);
}
divide(1, 0);
The error message than shows something like:
> php test-pad.php
Fatal error: Cannot divide by 0 in test-pad.php on line 18
Triggered in test-pad.php on line 15
The downside of this is, that you need to change your code to have this "feature". If you need this for debugging your own code, it's much better that you enable backtraces in your logs. The Xdebug extension does this for you, or you can write your own error handler that takes care of that.
See as well the related question Caller function in PHP 5?. I used array_slice so that you could create an additional parameter to define the number of steps you want to go "up" in the backtrace.
Use debug_backtrace(), and debug_print_backtrace() for a full call stack. These are especially effective when using Xdebug, which will override the function to colorize the output.
I have this same problem...
#1: while 10/0 = ERROR, 0/10 = 0 is perfectly legal, you shouldn't have an exception for that.
#2: when you include a file, it effectively becomes part of this new file, so perhaps you might have to toy a little bit with things like __FILE__ and see if you can make it point it to the file before it gets included in the other file..
You can use xdebug - it will show you the stacktrace or you can register your own error handndler and display the stacktrace. Just check the example in php.net for set_error_handler().
Maybe exceptions are better to use in your case. You get the full stacktrace and can locate where the function was called without relying on some tricky code :)

Does PHP copy variables when retrieving from shared memory?

If I run an shm_get_var(), will it return a "reference", keeping the data in shared memory?
I'm wanting to keep an array about 50MB in size in shared memory so that it can be used by multiple processes without having to keep multiple copies of this 50MB array hanging around. If shared memory isn't the answer, does anyone have another idea?
This is the relevant C code snippet from sysvsem.c in PHP 5.2.9 :
/* setup string-variable and serialize */
/* get serialized variable from shared memory */
shm_varpos = php_check_shm_data((shm_list_ptr->ptr), key);
if (shm_varpos < 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "variable key %ld doesn't exist", key);
RETURN_FALSE;
}
shm_var = (sysvshm_chunk*) ((char *)shm_list_ptr->ptr + shm_varpos);
shm_data = &shm_var->mem;
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (php_var_unserialize(&return_value, (const unsigned char **) &shm_data, shm_data + shm_var->length, &var_hash TSRMLS_CC) != 1) {
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "variable data in shared memory is corrupted");
RETURN_FALSE;
}
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
PHP will have to unserialize the entire value every time you call shm_get, which, on a 50MB array, is going to be really really slow.
How about breaking it up into individual values?
Also you might want to consider using APC's variable cache, which will handle all of the shared memory and locking for you (and will also use a hash table for key lookups)
I'm no expert on this, but would it be possible to write a quick test for this something like the following?
$key = 1234;
//put something small into shared memory
$identifier = shm_attach($key, 1024, 0777);
shm_put_var($identifier, $key, 'shave and a hair cut');
$firstVar = shm_get_var($identifier, $key);
$firstVar .= 'Test String of Doom';
$secondVar = shm_get_var($identifier, $key);
if ($firstVar == $secondVar) {
echo 'shm_get_var passes by reference';
} else {
echo 'shm_get_var passes by value';
}
form the wording of the documentation
shm_get_var() returns the variable
with a given variable_key , in the
given shared memory segment. The
variable is still present in the
shared memory.
I would say yes it's a reference to the shared memory space.
you can use shm_remove()
Check this out: http://php.net/manual/en/function.shm-remove.php

Categories