Skip to content
Closed
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: 39 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ handle multiple concurrent connections without blocking.
* [ConnectionInterface](#connectioninterface)
* [getRemoteAddress()](#getremoteaddress)
* [getLocalAddress()](#getlocaladdress)
* [UnixConnection](#connectioninterface)
* [getRemotePid()](#getremotepid)
* [getLocalPid()](#getlocalpid)
* [Server usage](#server-usage)
* [ServerInterface](#serverinterface)
* [connection event](#connection-event)
Expand Down Expand Up @@ -195,6 +198,36 @@ If your system has multiple interfaces (e.g. a WAN and a LAN interface),
you can use this method to find out which interface was actually
used for this connection.


### UnixConnection
The `UnixConnection` is a specific implementation of a `ConnectionInterface` used to represent any
incoming and outgoing connection over a Unix domain socket (UDS).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that consumers need to depend on a concretion rather instead of an abstraction?


#### getRemotePid()
The `getRemotePid(): ?int` method returns the PID where this connection has been established with.

```php
$pid = $connection->getRemotePid();
echo 'Remote PID: ' . $pid . PHP_EOL;
```

If the remote PID can not be determined or is unknown at this time (such as
after the connection has been closed), it MAY return a `NULL` value instead.
If the remote PID was already requested before the connection was closed the PID will still be cached.
The return value of this method should NOT be used to determine if the remote side of the socket is
The return value of this method should NOT be used to determine if the remote side of the socket is
still connected.

If the remote PID is known, it will return the PID as an integer value, such as `1640`.

#### getLocalPid()
The `getLocalPid(): ?int` method returns the PID where this connection has been established from (the current process).

```php
$pid = $connection->getLocalPid();
echo 'Local PID: ' . $pid . PHP_EOL;
```

## Server usage

### ServerInterface
Expand Down Expand Up @@ -706,11 +739,12 @@ $first = new UnixServer('/tmp/same.sock', $loop);
$second = new UnixServer('/tmp/same.sock', $loop);
```

Whenever a client connects, it will emit a `connection` event with a connection
instance implementing [`ConnectionInterface`](#connectioninterface):
Whenever a client connects, it will emit a `connection` event with a `UnixConnection`
instance implementing [`ConnectionInterface`](#connectioninterface). The `UnixConnection` has extra methods for
retrieving the PID (process identifier) from both sides of the socket (remote and local).

```php
$server->on('connection', function (ConnectionInterface $connection) {
$server->on('connection', function (UnixConnection $connection) {
echo 'New connection' . PHP_EOL;

$connection->write('hello there!' . PHP_EOL);
Expand Down Expand Up @@ -900,7 +934,7 @@ In order to create a local Unix domain socket connection, you can use the
`unix://` URI scheme like this:

```php
$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
$connector->connect('unix:///tmp/demo.sock')->then(function (UnixConnection $connection) {
$connection->write('...');
$connection->end();
});
Expand Down Expand Up @@ -1300,7 +1334,7 @@ Unix domain socket (UDS) paths like this:
```php
$connector = new React\Socket\UnixConnector($loop);

$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
$connector->connect('/tmp/demo.sock')->then(function (UnixConnection $connection) {
$connection->write("HELLO\n");
});

Expand Down
25 changes: 1 addition & 24 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@
*/
class Connection extends EventEmitter implements ConnectionInterface
{
/**
* Internal flag whether this is a Unix domain socket (UDS) connection
*
* @internal
*/
public $unix = false;

/**
* Internal flag whether encryption has been enabled on this connection
*
Expand Down Expand Up @@ -144,28 +137,12 @@ public function getLocalAddress()
return $this->parseAddress(@stream_socket_get_name($this->stream, false));
}

private function parseAddress($address)
protected function parseAddress($address)
{
if ($address === false) {
return null;
}

if ($this->unix) {
// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
// note that technically ":" is a valid address, so keep this in place otherwise
if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
$address = (string)substr($address, 0, -1);
}

// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
if ($address === '' || $address[0] === "\x00" ) {
return null;
}

return 'unix://' . $address;
}

// check if this is an IPv6 address which includes multiple colons but no square brackets
$pos = strrpos($address, ':');
if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
Expand Down
120 changes: 120 additions & 0 deletions src/UnixConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace React\Socket;

/**
* A UnixConnection is a specific implementation of a Connection for Unix connections.
*
* Unix connections have the ability to retrieve the PID of the local and remote side of the socket.
*
* @see Connection
*/
class UnixConnection extends Connection

@clue clue Jul 2, 2018 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the Connection, this class is currently not marked as @internal. Do we want this to become part of our public API?

{
/**
* @see http://php.net/manual/en/function.get-resource-type.php
*/
const RESOURCE_TYPE_STREAM = 'stream';

/**
* PHP has no built in constant for retrieving the credentials of the peer process.
*
* @see http://php.net/manual/en/sockets.constants.php
* @see http://php.net/manual/en/function.socket-get-option.php#101380
*/
const SO_PEERCRED = 17;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be platform dependent (Linux only?). For example, FreeBSD (and Mac?) seems to use LOCAL_PEERCRED instead?


/**
* @var int|null Variable to cache the remote pid.
*/
private $remote_pid;

/**
* @var int|null Variable to cache the local pid.
*/
private $local_pid;

protected function parseAddress($address)
{
if ($address === false) {
return null;
}

// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
// note that technically ":" is a valid address, so keep this in place otherwise
if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
$address = (string)substr($address, 0, -1);
}

// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
if ($address === '' || $address[0] === "\x00") {
return null;
}

return 'unix://' . $address;
}

/**
* Retrieve the PID (process identifier) of the remote side of the Connection. The pid will be cached to avoid
* requesting the value multiple times. If the remote pid was not cached before the connection is closed, the remote
* pid cannot be retrieved anymore.
*
* Warning: Process IDs are not unique, thus they are a weak entropy source. Relying on pids in security-dependent
* contexts should be avoided.
*
* @return int|null
*/
public function getRemotePid()
{
// If the remote pid has already been cached, return that value.
if ($this->remote_pid !== null) {
return $this->remote_pid;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation for caching the PID here?


if (get_resource_type($this->stream) !== self::RESOURCE_TYPE_STREAM) {
return null;
}

$socket = socket_import_stream($this->stream);

if ($socket === false || $socket === null) {
return null;
}

// Get the PID of the remote side of the socket.
$pid = socket_get_option($socket, SOL_SOCKET, self::SO_PEERCRED);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if ext-sockets is not available?


if ($pid === false) {
return null;
}

$this->remote_pid = (int)$pid;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the discussion in #150:

Interestingly SO_PEERCRED socket option should return a ucred structure with pid, uid and gid. Right now, PHP does not really support this constant and simply seems to return the first element only.

What if PHP starts supporting SO_PEERCRED? It's my understanding that this may break in a future PHP version?


return $this->remote_pid;
}

/**
* Retrieve the PID (process identifier) of the local side of the Connection.
*
* Warning: Process IDs are not unique, thus they are a weak entropy source. Relying on pids in security-dependent
* contexts should be avoided.
*
* @return int|null
*/
public function getLocalPid()
{
if ($this->local_pid !== null) {
return $this->local_pid;
}

$pid = getmypid();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation for this method? Looks like this should not be part of the socket API?

if ($pid === false) {
return null;
}

$this->local_pid = $pid;

return $this->local_pid;
}
}
3 changes: 1 addition & 2 deletions src/UnixConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ public function connect($path)
return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));
}

$connection = new Connection($resource, $this->loop);
$connection->unix = true;
$connection = new UnixConnection($resource, $this->loop);

return Promise\resolve($connection);
}
Expand Down
3 changes: 1 addition & 2 deletions src/UnixServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ public function close()
/** @internal */
public function handleConnection($socket)
{
$connection = new Connection($socket, $this->loop);
$connection->unix = true;
$connection = new UnixConnection($socket, $this->loop);

$this->emit('connection', array(
$connection
Expand Down
Loading