opcua-client-ext-reverse-connect · v4.4.x
Docs · Recipes

Docker host networking

Make the in-container OPC UA server reach a listener that lives on the docker host — the network plumbing the integration tests and the example script rely on.

Reverse Connect inverts the TCP direction: the server connects to the client. When the server runs inside a Docker container and the client runs on the host, the in-container address 127.0.0.1 refers to the container itself, not the host. Two pieces have to line up.

1 — Make the host reachable from the container

Add host.docker.internal as a host-gateway alias on the service that should be able to dial out. The uanetstandard-test-suite v1.4.0 docker-compose.yml already does this for its opcua-no-security service:

services:
  opcua-no-security:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Inside the container, host.docker.internal then resolves to the docker host. Verify with:

docker exec <container> getent ahosts host.docker.internal
# 192.168.65.254  STREAM host.docker.internal
# 192.168.65.254  DGRAM  host.docker.internal

The exact IP depends on the docker network — what matters is that the name resolves to an address the container can route to.

2 — Bind the listener on 0.0.0.0

The listener must accept the incoming TCP connection from the docker bridge IP, not just loopback. Construct it with '0.0.0.0' as bindHost:

$listener = new ReverseConnectListener(
    bindHost: '0.0.0.0',
    bindPort: 0,                  // kernel-assigned
    validator: new ReverseHelloValidator(['urn:opcua:testserver:nodes']),
);
$listener->listen();
[, $port] = explode(':', $listener->getBindAddress());

bindPort: 0 lets the kernel pick a free port; the resulting port is what you then hand to the server in step 3.

3 — Tell the server to dial host.docker.internal:<port>

Call the trigger method (in the test suite, TestServer/ReverseConnect/StartReverseConnect) with 'host.docker.internal' as the host and the kernel-assigned port as the value:

use PhpOpcua\Client\Types\BuiltinType;
use PhpOpcua\Client\Types\NodeId;
use PhpOpcua\Client\Types\Variant;

$trigger->call(
    NodeId::string(2, 'TestServer/ReverseConnect'),
    NodeId::string(2, 'TestServer/ReverseConnect/StartReverseConnect'),
    [
        new Variant(BuiltinType::String, 'host.docker.internal'),
        new Variant(BuiltinType::UInt16, $port),
    ],
);

The combination of extra_hosts + bind on 0.0.0.0 + the alias as the trigger host gets the server-side TCP connection back to the listener.

Non-docker hosts

If the server runs on a different physical machine or VM, replace 'host.docker.internal' with the actual reachable address (DNS name or IP) of the host running the listener — and make sure any firewall between them allows inbound TCP on the listener port. The listener code itself does not change.

Listening on a single interface

If the listener has to stay loopback-only (no docker, no remote servers), use '127.0.0.1' as bindHost. The trigger then has to be something local — for example a second PHP process on the same host — because nothing else can reach the loopback interface.