Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions configs/routeros-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
|
*/

'host' => env('ROUTEROS_HOST', '192.168.88.1'), // Address of Mikrotik RouterOS
'user' => env('ROUTEROS_USER', 'admin'), // Username
'pass' => env('ROUTEROS_PASS'), // Password
'port' => (int) env('ROUTEROS_PORT', 8728), // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'host' => env('ROUTEROS_HOST', '192.168.88.1'), // Address of Mikrotik RouterOS
'user' => env('ROUTEROS_USER', 'admin'), // Username
'pass' => env('ROUTEROS_PASS'), // Password
'port' => (int) env('ROUTEROS_PORT', 8728), // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)

/*
|--------------------------------------------------------------------------
Expand All @@ -30,14 +30,14 @@
|
*/

'attempts' => (int) env('ROUTEROS_ATTEMPTS', 10), // Count of attempts to establish TCP session
'delay' => (int) env('ROUTEROS_DELAY', 1), // Delay between attempts in seconds
'timeout' => (int) env('ROUTEROS_TIMEOUT', 10), // Max timeout for instantiating connection with RouterOS
'socket_timeout' => (int) env('ROUTEROS_SOCKET_TIMEOUT', env('ROUTEROS_TIMEOUT', 30)), // Max timeout for read from RouterOS
'socket_blocking' => (bool) env('ROUTEROS_SOCKET_BLOCKING', true), // Set blocking mode on a socket stream
'attempts' => (int) env('ROUTEROS_ATTEMPTS', 10), // Count of attempts to establish TCP session
'delay' => (int) env('ROUTEROS_DELAY', 1), // Delay between attempts in seconds
'timeout' => (int) env('ROUTEROS_TIMEOUT', 10), // Max timeout for instantiating connection with RouterOS
'socket_timeout' => (int) env('ROUTEROS_SOCKET_TIMEOUT', env('ROUTEROS_TIMEOUT', 30)), // Max timeout for read from RouterOS
'socket_blocking' => (bool) env('ROUTEROS_SOCKET_BLOCKING', true), // Set blocking mode on a socket stream

// @see https://www.php.net/manual/en/context.socket.php
'socket_options' => [
'socket_options' => [
// Examples:
// 'bindto' => '192.168.0.100:0', // connect to the internet using the '192.168.0.100' IP
// 'bindto' => '192.168.0.100:7000', // connect to the internet using the '192.168.0.100' IP and port '7000'
Expand All @@ -61,10 +61,10 @@
|
*/

'ssl' => (bool) env('ROUTEROS_SSL', false), // Enable ssl support (if port is not set this parameter must change default port to ssl port)
'ssl' => (bool) env('ROUTEROS_SSL', false), // Enable ssl support (if port is not set this parameter must change default port to ssl port)

// @see https://www.php.net/manual/en/context.ssl.php
'ssl_options' => [
'ssl_options' => [
'ciphers' => env('ROUTEROS_SSL_CIPHERS', 'ADH:ALL'), // ADH:ALL, ADH:ALL@SECLEVEL=0, ADH:ALL@SECLEVEL=1 ... ADH:ALL@SECLEVEL=5
'verify_peer' => (bool) env('ROUTEROS_SSL_VERIFY_PEER', false), // Require verification of SSL certificate used.
'verify_peer_name' => (bool) env('ROUTEROS_SSL_VERIFY_PEER_NAME', false), // Require verification of peer name.
Expand All @@ -81,9 +81,9 @@
|
*/

'ssh_port' => (int) env('ROUTEROS_SSH_PORT', 22), // Number of SSH port
'ssh_timeout' => (int) env('ROUTEROS_SSH_TIMEOUT', env('ROUTEROS_TIMEOUT', 30)), // Max timeout for read from RouterOS via SSH proto (for "/export" command)
'ssh_private_key' => env('ROUTEROS_SSH_PRIVKEY', '~/.ssh/id_rsa.pub'), // Full path to required private key
'ssh_port' => (int) env('ROUTEROS_SSH_PORT', 22), // Number of SSH port
'ssh_timeout' => (int) env('ROUTEROS_SSH_TIMEOUT', env('ROUTEROS_TIMEOUT', 30)), // Max timeout for read from RouterOS via SSH proto (for "/export" command)
'ssh_private_key' => env('ROUTEROS_SSH_PRIVKEY', '~/.ssh/id_rsa.pub'), // Full path to required private key

/*
|--------------------------------------------------------------------------
Expand All @@ -95,6 +95,18 @@
|
*/

'legacy' => (bool) env('ROUTEROS_LEGACY', false), // Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'legacy' => (bool) env('ROUTEROS_LEGACY', false), // Support of legacy login scheme (true - pre 6.43, false - post 6.43)

/*
|--------------------------------------------------------------------------
| PHP 8.4 Compatibility
|--------------------------------------------------------------------------
|
| If you experience "Stream timed out" errors on PHP 8.4, you can disable
| the timeout exception by setting this to false.
|
*/

'throw_timeout_exception' => (bool) env('ROUTEROS_THROW_TIMEOUT_EXCEPTION', true), // Throw exception on stream timeout

];
4 changes: 3 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,9 @@ public function connect(): bool

// If socket is active
if (null !== $this->getSocket()) {
$this->connector = new APIConnector(new Streams\ResourceStream($this->getSocket()));
$stream = new Streams\ResourceStream($this->getSocket());
$stream->setThrowTimeoutException($this->config('throw_timeout_exception'));
$this->connector = new APIConnector($stream);
// If we logged in then exit from loop
if (true === $this->login()) {
$connected = true;
Expand Down
64 changes: 36 additions & 28 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,26 +122,33 @@ class Config implements ConfigInterface

public const SSH_PRIVATE_KEY = '~/.ssh/id_rsa';

/**
* By default throw exception on stream timeout
* Set to false to disable timeout exception (useful for PHP 8.4 compatibility issues)
*/
public const THROW_TIMEOUT_EXCEPTION = true;

/**
* List of allowed parameters of config
*/
public const ALLOWED = [
'host' => 'string', // Address of Mikrotik RouterOS
'user' => 'string', // Username
'pass' => 'string', // Password
'port' => 'integer', // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'ssl' => 'boolean', // Enable ssl support (if port is not set this parameter must change default port to ssl port)
'ssl_options' => 'array', // List of SSL options, eg.
'legacy' => 'boolean', // Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'timeout' => 'integer', // Max timeout for instantiating connection with RouterOS
'socket_timeout' => 'integer', // Max timeout for read from RouterOS
'socket_blocking' => 'boolean', // Set blocking mode on a socket stream
'socket_options' => 'array', // List of socket context options
'attempts' => 'integer', // Count of attempts to establish TCP session
'delay' => 'integer', // Delay between attempts in seconds
'ssh_port' => 'integer', // Number of SSH port
'ssh_timeout' => 'integer', // Max timeout for read from RouterOS via SSH proto (for "/export" command)
'ssh_private_key' => 'string', // Max timeout for read from RouterOS via SSH proto (for "/export" command)
'host' => 'string', // Address of Mikrotik RouterOS
'user' => 'string', // Username
'pass' => 'string', // Password
'port' => 'integer', // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'ssl' => 'boolean', // Enable ssl support (if port is not set this parameter must change default port to ssl port)
'ssl_options' => 'array', // List of SSL options, eg.
'legacy' => 'boolean', // Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'timeout' => 'integer', // Max timeout for instantiating connection with RouterOS
'socket_timeout' => 'integer', // Max timeout for read from RouterOS
'socket_blocking' => 'boolean', // Set blocking mode on a socket stream
'socket_options' => 'array', // List of socket context options
'attempts' => 'integer', // Count of attempts to establish TCP session
'delay' => 'integer', // Delay between attempts in seconds
'ssh_port' => 'integer', // Number of SSH port
'ssh_timeout' => 'integer', // Max timeout for read from RouterOS via SSH proto (for "/export" command)
'ssh_private_key' => 'string', // Full path to required private key
'throw_timeout_exception' => 'boolean', // Throw exception on stream timeout (set false to disable for PHP 8.4 issues)
];

/**
Expand All @@ -150,18 +157,19 @@ class Config implements ConfigInterface
* @var array
*/
private $_parameters = [
'legacy' => self::LEGACY,
'ssl' => self::SSL,
'ssl_options' => self::SSL_OPTIONS,
'timeout' => self::TIMEOUT,
'socket_timeout' => self::SOCKET_TIMEOUT,
'socket_blocking' => self::SOCKET_BLOCKING,
'socket_options' => self::SOCKET_OPTIONS,
'attempts' => self::ATTEMPTS,
'delay' => self::ATTEMPTS_DELAY,
'ssh_port' => self::SSH_PORT,
'ssh_timeout' => self::SSH_TIMEOUT,
'ssh_private_key' => self::SSH_PRIVATE_KEY,
'legacy' => self::LEGACY,
'ssl' => self::SSL,
'ssl_options' => self::SSL_OPTIONS,
'timeout' => self::TIMEOUT,
'socket_timeout' => self::SOCKET_TIMEOUT,
'socket_blocking' => self::SOCKET_BLOCKING,
'socket_options' => self::SOCKET_OPTIONS,
'attempts' => self::ATTEMPTS,
'delay' => self::ATTEMPTS_DELAY,
'ssh_port' => self::SSH_PORT,
'ssh_timeout' => self::SSH_TIMEOUT,
'ssh_private_key' => self::SSH_PRIVATE_KEY,
'throw_timeout_exception' => self::THROW_TIMEOUT_EXCEPTION,
];

/**
Expand Down
29 changes: 26 additions & 3 deletions src/Streams/ResourceStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ class ResourceStream implements StreamInterface
{
protected $stream;

/**
* Whether to throw exception on stream timeout
*
* @var bool
*/
protected $throwTimeoutException = true;

/**
* ResourceStream constructor.
*
Expand All @@ -32,6 +39,18 @@ public function __construct($stream)
$this->stream = $stream;
}

/**
* Set whether to throw exception on stream timeout
*
* @param bool $throw
* @return self
*/
public function setThrowTimeoutException(bool $throw): self
{
$this->throwTimeoutException = $throw;
return $this;
}

/**
* {@inheritDoc}
*
Expand All @@ -50,9 +69,13 @@ public function read(int $length): string

$result = fread($this->stream, $length);

// Stream in blocking mode timed out
if(socket_get_status($this->stream)['timed_out']){
throw new StreamException('Stream timed out');
// PHP 8.4 may report timed_out=true even on successful partial reads
// Only throw timeout if we got no data AND stream actually timed out
if ($this->throwTimeoutException) {
$info = stream_get_meta_data($this->stream);
if ($info['timed_out'] && ($result === '' || $result === false)) {
throw new StreamException('Stream timed out');
}
}

if (false === $result) {
Expand Down