EDIT
The previous proxy code, after thorough testing, had issues with the stream_copy_to_stream()
when sending from $client
stream to $dest
stream, and vice versa. I have rewritten the code to remedy that issue:
set_time_limit(0); // Run server indefinitely, or until SIGINT (CTRL+c)
$destination = '172.16.12.1:125'; // Where should we connect when client connects to us?
$server_bind = '0.0.0.0:3002'; // Which address:port should we bind to?
// Start the server on address:port
$server = @stream_socket_server("$server_bind", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
if(!$server) die("[ERROR] Failed to bind to $server_bind. Exiting.\r\n");
print_r("[INFO] Listening on $server_bind\r\n");
// Run server forever
while(true) {
// Wait 10 seconds for client connection
$client = @stream_socket_accept($server, 10, $peer);
// If client connects...
if($client) {
// ... Set the stream blocking to false
stream_set_blocking($client, false);
print_r("[INFO] $peer-<>-$server_bind-<>-$destination-");
$dest = stream_socket_client($destination, $errno, $errstr, 10, STREAM_CLIENT_CONNECT);
// If connection to $destination successful...
if($dest) {
// ... Set the stream blocking to false
stream_set_blocking($dest, false);
// Print successful connection to shell
print_r("<>-OK\r\n");
// Fork the connection to child
$pid = pcntl_fork();
// die() if fork fails for some reason
if($pid === -1) die("[ERROR] $peer-<>-$server_bind-<>-$destination-<--Fork failed. Exiting.\r\n");
// Continue "while(true)" if we are parent
elseif($pid) continue;
// If we are child...
else {
// ... Loop indefinitely
while(true) {
// Create socket arrays containing $client and $dest
$sockets = $w_sockets = [$client, $dest];
// If either side disconnects, shutdown both streams
if(feof($client) || feof($dest)) {
print_r("[INFO] $peer-<>-$server_bind-<>-$destination-<>-Disconnect\r\n");
foreach($sockets as $id => $stream) stream_socket_shutdown($stream, STREAM_SHUT_RDWR);
// Break from this "while(true)" to exit(0)
break;
}
// Block until either stream updates its read status
stream_select($sockets, $w = NULL, $e = NULL, NULL);
// Block until either stream updates its write stats
stream_select($r = NULL, $w_sockets, $e = NULL, NULL);
// ... run ternary conditional to determine which stream to copy from/to
// If $client stream updated and $dest stream writable, copy its contents to $dest, otherwise copy $dest contents to $client
in_array($client, $sockets) && in_array($dest, $w_sockets) ? stream_copy_to_stream($client, $dest, 1024) : stream_copy_to_stream($dest, $client, 1024);
}
// Exit child process after $client/$dest disconnect
exit(0);
}
}
// If connection to $destination fails...
else {
// ... print the failure
print_r("<--FAIL\r\n");
// Tell client of failure
fwrite($client, "Connection to $destination failed. You are being disconnected.\r\n");
// Shutdown $client stream
stream_socket_shutdown($client, STREAM_SHUT_RDWR);
// Break from first "while(true)" to EOF
break;
}
}
}
With the use of pcntl_fork()
, this cannot work on Windows. I'm not as well-versed in PHP sockets as I'd like, and would like to make a version of this that can work on Windows in the same fashion (i.e. accept multiple simultaneous connections). What modifications can/must I make in order to produce a copy that would work on Windows as well?
In a fit of spontaneity, I wrote a quick-n-dirty TCP proxy in PHP:
set_time_limit(0); // Run server indefinitely, or until SIGINT (CTRL+c)
$destination = '127.0.0.1:25'; // Where should we connect when client connects to us?
$server_addr = '0.0.0.0'; // Which address should we bind to?
$server_port = '3002'; // Which port should we bind to?
// Start the server on address:port
$server = @stream_socket_server("$server_addr:$server_port", $errno, $errstr);
// Run server forever
while(true) {
// Wait 10 seconds for client connection
$client = @stream_socket_accept($server, 10, $peer);
// If client connects...
if($client) {
// ... Set the stream blocking to false
stream_set_blocking($client, false);
// Attempt connection to $destination
$dest = @stream_socket_client($destination, $errno, $errstr, 10);
// If connection to $destination successful...
if($dest) {
// ... Set the stream blocking to false
stream_set_blocking($dest, false);
// Print successful connection to shell
print_r("[CONNECT] $peer-<><>-$destination\r\n");
// Fork the connection to child
$pid = pcntl_fork();
// die() if fork fails for some reason
if($pid === -1) die("[ERROR] Unable to fork connection. Exiting.\r\n");
// Continue "while(true)" if we are parent
elseif($pid) continue;
// If we are child...
else {
// ... Loop indefinitely
while(true) {
// Create socket array for $client and $dest
$sockets = [$client, $dest];
// If either side disconnects, shutdown both streams
if(feof($client) || feof($dest)) {
print_r("[DISCONNECT] $peer-<><>-$destination\r\n");
for($i = 0; $i < count($sockets); $i++) stream_socket_shutdown($sockets[$i], STREAM_SHUT_RDWR);
// Break from this "while(true)" to exit(0)
break;
}
// EDIT: removed tv_usec parameter to block until either stream updates
// Only run in_array() ternary conditional if stream_select() returns "truthy" value
if(stream_select($sockets, $w = NULL, $e = NULL, NULL))
// If $client stream updated, copy its contents to $dest.
// Otherwise copy $dest contents to $client
in_array($client, $sockets) ? stream_copy_to_stream($client, $dest) : stream_copy_to_stream($dest, $client);
}
// Exit child process after $client/$dest disconnect
exit(0);
}
}
// If connection to $destination fails...
else {
// ... print the failure
print_r("[FAIL] $peer-<><>-$destination\r\n");
// Shutdown $client stream
stream_socket_shutdown($client, STREAM_SHUT_RDWR);
// Break from first "while(true)" to EOF
break;
}
}
}