Skip to content

Commit 0d57895

Browse files
committed
Try more elaborate unix socket proxy
1 parent 0f35f5d commit 0d57895

2 files changed

Lines changed: 216 additions & 29 deletions

File tree

tests/ext/includes/request_replayer.inc

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ ini_set("datadog.trace.httpstream_enabled", 0);
66

77
class ProxyContainer {
88
public $proc;
9-
public function __construct($proc) {
10-
$this->proc = $proc;
11-
}
9+
private $pipes;
1210

13-
public function __destruct() {
14-
proc_terminate($this->proc, 9);
11+
public function __construct($proc, array $pipes) {
12+
$this->proc = $proc;
13+
$this->pipes = $pipes;
1514
}
1615
}
1716

@@ -203,32 +202,34 @@ class RequestReplayer
203202
]));
204203
}
205204

206-
public static function launchUnixProxy($socketPath) {
205+
/**
206+
* Serves $socketPath and forwards everything to the request-replayer, so
207+
* that the tracer's unix socket agent transport can be tested.
208+
*
209+
* Launches unix_socket_proxy.php.
210+
*/
211+
public static function launchUnixProxy($socketPath, $upstream = "request-replayer:80") {
207212
@unlink($socketPath);
208-
$code = str_replace("\n", "", '
209-
ignore_user_abort(true); /* prevent bailout... */
210-
$server = stream_socket_server("unix://' . $socketPath . '");
211-
print "1\n"; /* ready marker */
212-
while ($client = stream_socket_accept($server, 5)) {
213-
file_put_contents("/tmp/unix-proxy-' . basename($socketPath) . '", "connected\n", FILE_APPEND);
214-
$replayer = stream_socket_client("request-replayer:80");
215-
$all = $read = [$client, $replayer];
216-
foreach ($read as $fp) stream_set_blocking($fp, false);
217-
while (stream_select($read, $w, $e, null)) {
218-
$data = fread($fp = reset($read), 4096);
219-
if ($data == "") {
220-
file_put_contents("/tmp/unix-proxy-' . basename($socketPath) . '", "end\n", FILE_APPEND);
221-
break;
213+
214+
// -n: run without any php.ini, so the proxy does not itself load
215+
// ddtrace and send agent requests into the socket it is serving.
216+
$cmd = PHP_BINARY . " -n " . escapeshellarg(__DIR__ . "/unix_socket_proxy.php")
217+
. " " . escapeshellarg($socketPath) . " " . escapeshellarg($upstream);
218+
// stdin is a pipe on purpose: the proxy exits once it hits EOF there,
219+
// which happens as soon as this process is gone. That avoids both a
220+
// leaked proxy and the previous inactivity timeout, which killed the
221+
// proxy while a test was still running.
222+
$proc = proc_open($cmd, [["pipe", "r"], ["pipe", "w"], STDERR], $pipes);
223+
if (!is_resource($proc)) {
224+
throw new Exception("failed to launch unix proxy for $socketPath");
225+
}
226+
227+
if (fread($pipes[1], 1) !== "1") { // ready marker
228+
fclose($pipes[0]);
229+
proc_close($proc);
230+
throw new Exception("unix proxy for $socketPath failed to start");
222231
}
223-
file_put_contents("/tmp/unix-proxy-' . basename($socketPath) . '", "$data\n", FILE_APPEND);
224-
fwrite($fp == $replayer ? $client : $replayer, $data);
225-
$read = $all;
226-
}
227-
}
228-
');
229232

230-
$proc = proc_open(PHP_BINARY . " -r '$code'", [STDIN, ["pipe", "w"], STDERR], $pipes);
231-
fread($pipes[1], 1); // ready
232-
return new ProxyContainer($proc);
233+
return new ProxyContainer($proc, [$pipes[0]]);
233234
}
234235
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php
2+
3+
/**
4+
* Bidirectional proxy from a unix domain socket to the request-replayer, used
5+
* by .phpt tests that exercise the tracer's unix socket agent transport.
6+
*
7+
* Launched by RequestReplayer::launchUnixProxy(), not meant to be run directly.
8+
*
9+
* Usage: php unix_socket_proxy.php <socket-path> [<host:port>] [<log-file>]
10+
*
11+
* Parent protocol:
12+
* - prints "1\n" on stdout once the socket accepts connections
13+
* - exits once stdin hits EOF, i.e. once the test process launching it is gone
14+
*/
15+
16+
const CHUNK_SIZE = 65536;
17+
/* Stop reading from a socket while this much data is still queued for its peer. */
18+
const MAX_BUFFERED = 4 * CHUNK_SIZE;
19+
20+
if (!isset($argv[1])) {
21+
fwrite(STDERR, "usage: unix_socket_proxy.php <socket-path> [<host:port>] [<log-file>]\n");
22+
exit(1);
23+
}
24+
25+
$socketPath = $argv[1];
26+
$upstreamAddr = isset($argv[2]) ? $argv[2] : 'request-replayer:80';
27+
$logFile = isset($argv[3]) ? $argv[3] : '/tmp/unix-proxy-' . basename($socketPath);
28+
29+
ignore_user_abort(true);
30+
31+
@unlink($socketPath);
32+
$server = @stream_socket_server('unix://' . $socketPath, $errno, $errstr);
33+
if (!$server) {
34+
fwrite(STDERR, "unix_socket_proxy: cannot listen on $socketPath: $errstr ($errno)\n");
35+
exit(1);
36+
}
37+
stream_set_blocking($server, false);
38+
39+
echo "1\n"; /* ready marker, awaited by launchUnixProxy() */
40+
flush();
41+
42+
/* id => ['client' => resource, 'upstream' => resource, 'out' => [side => string], ...] */
43+
$conns = [];
44+
/* (int) resource => [id, side] */
45+
$owner = [];
46+
$nextId = 0;
47+
48+
function other_side($side)
49+
{
50+
return $side === 'client' ? 'upstream' : 'client';
51+
}
52+
53+
/**
54+
* Tests grep the proxied bytes out of this file, so keep the format as is:
55+
* one chunk per write, each starting on a fresh line.
56+
*/
57+
function proxy_log($data)
58+
{
59+
global $logFile;
60+
file_put_contents($logFile, $data . "\n", FILE_APPEND);
61+
}
62+
63+
function close_pair($id)
64+
{
65+
global $conns, $owner;
66+
foreach (['client', 'upstream'] as $side) {
67+
$stream = $conns[$id][$side];
68+
unset($owner[(int) $stream]);
69+
@fclose($stream);
70+
}
71+
unset($conns[$id]);
72+
}
73+
74+
function accept_pending()
75+
{
76+
global $server, $upstreamAddr, $conns, $owner, $nextId;
77+
78+
while (($client = @stream_socket_accept($server, 0)) !== false) {
79+
$upstream = @stream_socket_client('tcp://' . $upstreamAddr, $errno, $errstr, 5);
80+
if (!$upstream) {
81+
proxy_log("upstream connect to $upstreamAddr failed: $errstr ($errno)");
82+
fclose($client);
83+
continue;
84+
}
85+
stream_set_blocking($client, false);
86+
stream_set_blocking($upstream, false);
87+
88+
$id = $nextId++;
89+
$conns[$id] = [
90+
'client' => $client,
91+
'upstream' => $upstream,
92+
'out' => ['client' => '', 'upstream' => ''],
93+
'eof' => ['client' => false, 'upstream' => false],
94+
'shutdown' => ['client' => false, 'upstream' => false],
95+
];
96+
$owner[(int) $client] = [$id, 'client'];
97+
$owner[(int) $upstream] = [$id, 'upstream'];
98+
proxy_log('connected');
99+
}
100+
}
101+
102+
while (true) {
103+
$read = [STDIN, $server];
104+
$write = [];
105+
foreach ($conns as $id => $conn) {
106+
foreach (['client', 'upstream'] as $side) {
107+
if (!$conn['eof'][$side] && strlen($conn['out'][other_side($side)]) < MAX_BUFFERED) {
108+
$read[] = $conn[$side];
109+
}
110+
if ($conn['out'][$side] !== '') {
111+
$write[] = $conn[$side];
112+
}
113+
}
114+
}
115+
116+
$except = null;
117+
if (@stream_select($read, $write, $except, null) === false) {
118+
break; /* interrupted by a signal */
119+
}
120+
121+
foreach ($read as $stream) {
122+
if ($stream === STDIN) {
123+
/* The test process that launched us is gone. */
124+
@unlink($socketPath);
125+
exit(0);
126+
}
127+
128+
if ($stream === $server) {
129+
accept_pending();
130+
continue;
131+
}
132+
133+
if (!isset($owner[(int) $stream])) {
134+
continue; /* closed while handling an earlier stream in this batch */
135+
}
136+
list($id, $side) = $owner[(int) $stream];
137+
138+
$data = @fread($stream, CHUNK_SIZE);
139+
if ($data === false || $data === '') {
140+
$conns[$id]['eof'][$side] = true;
141+
proxy_log('end');
142+
} else {
143+
proxy_log($data);
144+
$conns[$id]['out'][other_side($side)] .= $data;
145+
}
146+
}
147+
148+
foreach ($conns as $id => $conn) {
149+
foreach (['client', 'upstream'] as $side) {
150+
if ($conns[$id]['out'][$side] === '') {
151+
continue;
152+
}
153+
/* Sockets are non-blocking: a short write is normal, keep the rest queued. */
154+
$written = @fwrite($conns[$id][$side], $conns[$id]['out'][$side]);
155+
if ($written === false) {
156+
$conns[$id]['out'][$side] = '';
157+
$conns[$id]['eof'][$side] = true;
158+
continue;
159+
}
160+
$conns[$id]['out'][$side] = substr($conns[$id]['out'][$side], $written);
161+
}
162+
}
163+
164+
foreach ($conns as $id => $conn) {
165+
/*
166+
* Forward each half-close once everything read before it has been
167+
* handed over. Responses delimited by connection close depend on this.
168+
*/
169+
foreach (['client', 'upstream'] as $side) {
170+
$peer = other_side($side);
171+
if ($conns[$id]['eof'][$side]
172+
&& $conns[$id]['out'][$peer] === ''
173+
&& !$conns[$id]['shutdown'][$peer]
174+
) {
175+
@stream_socket_shutdown($conns[$id][$peer], STREAM_SHUT_WR);
176+
$conns[$id]['shutdown'][$peer] = true;
177+
}
178+
}
179+
180+
if ($conns[$id]['eof']['client'] && $conns[$id]['eof']['upstream']
181+
&& $conns[$id]['out']['client'] === '' && $conns[$id]['out']['upstream'] === ''
182+
) {
183+
close_pair($id);
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)