22declare(strict_types=1);
 
   36require dirname(__DIR__) . 
'/vendor/autoload.php';
 
   41if(count($argv) === 3){
 
   42    $serverAddress = $argv[1];
 
   43    $serverPort = (int) $argv[2];
 
   44}elseif(count($argv) === 1){
 
   45    echo 
"Enter server address: ";
 
   46    $serverAddress = fgets(STDIN);
 
   47    if($serverAddress === 
false){ 
 
   50    $serverAddress = trim($serverAddress);
 
   51    echo 
"Enter server port: ";
 
   52    $input = fgets(STDIN);
 
   56    $serverPort = (int) trim($input);
 
   58    echo 
"Usage: php proxy.php [bind address] [bind port]\n";
 
   62$serverAddress = gethostbyname($serverAddress);
 
   64if($serverPort !== 19132){
 
   65    \GlobalLogger::get()->warning(
"You may experience problems connecting to PocketMine-MP servers on ports other than 19132 if the server has port checking enabled");
 
   68\GlobalLogger::get()->info(
"Opening listen socket");
 
   71    $proxyToServerUnconnectedSocket->setBlocking(
false);
 
   73    \GlobalLogger::get()->emergency(
"Can't connect to $serverAddress on port $serverPort, is the server online?");
 
   74    \GlobalLogger::get()->emergency($e->getMessage());
 
   77socket_getsockname($proxyToServerUnconnectedSocket->getSocket(), $proxyAddr, $proxyPort);
 
   78\GlobalLogger::get()->info(
"Listening on $bindAddr:$bindPort, sending from $proxyAddr:$proxyPort, sending to $serverAddress:$serverPort");
 
   82    $clientProxySocket->setBlocking(
false);
 
   84    \GlobalLogger::get()->emergency(
"Can't bind to $bindAddr on port $bindPort, is something already using that port?");
 
   85    \GlobalLogger::get()->emergency($e->getMessage());
 
   89\GlobalLogger::get()->info(
"Press CTRL+C to stop the proxy");
 
   91$clientAddr = $clientPort = 
null;
 
   94    private int $lastUsedTime;
 
   96    public function __construct(
 
  100        $this->lastUsedTime = time();
 
  104        return $this->address;
 
  108        return $this->proxyToServerSocket;
 
  111    public function setActive() : 
void{
 
  112        $this->lastUsedTime = time();
 
  115    public function isActive() : 
bool{
 
  116        return time() - $this->lastUsedTime < 10;
 
 
  121    $buffer = $client->getSocket()->readPacket();
 
  122    if($buffer !== 
null){
 
  123        $clientProxySocket->
writePacket($buffer, $client->getAddress()->getIp(), $client->getAddress()->getPort());
 
  130$serverId = mt_rand(0, Limits::INT32_MAX);
 
  131$mostRecentPong = 
null;
 
  136    $r[++$k] = $clientProxySocket->getSocket();
 
  137    $r[++$k] = $proxyToServerUnconnectedSocket->getSocket();
 
  139    foreach($clients as $ipClients){
 
  140        foreach($ipClients as $client){
 
  142            $r[$key] = $client->getSocket()->getSocket();
 
  143            $clientIndex[$key] = $client;
 
  147    if(socket_select($r, $w, $e, 10) > 0){
 
  148        foreach($r as $key => $socket){
 
  149            if(isset($clientIndex[$key])){
 
  150                serverToClientRelay($clientIndex[$key], $clientProxySocket);
 
  151            }elseif($socket === $proxyToServerUnconnectedSocket->getSocket()){
 
  152                $buffer = $proxyToServerUnconnectedSocket->readPacket();
 
  153                if($buffer !== 
null && $buffer !== 
"" && ord($buffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PONG){
 
  154                    $mostRecentPong = $buffer;
 
  155                    \GlobalLogger::get()->info(
"Caching ping response from server: " . $buffer);
 
  157            }elseif($socket === $clientProxySocket->getSocket()){
 
  159                    $buffer = $clientProxySocket->
readPacket($recvAddr, $recvPort);
 
  161                    $error = $e->getCode();
 
  162                    if($error === SOCKET_ECONNRESET){ 
 
  166                    \GlobalLogger::get()->error(
"Socket error: " . $e->getMessage());
 
  170                if($buffer === 
null || $buffer === 
""){
 
  173                assert($recvAddr !== 
null, 
"Can't be null if we got a buffer");
 
  174                assert($recvPort !== 
null, 
"Can't be null if we got a buffer");
 
  175                if(isset($clients[$recvAddr][$recvPort])){
 
  176                    $client = $clients[$recvAddr][$recvPort];
 
  177                    $client->setActive();
 
  178                    $client->getSocket()->writePacket($buffer);
 
  179                }elseif(ord($buffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PING){
 
  180                    \GlobalLogger::get()->info(
"Got ping from $recvAddr on port $recvPort, pinging server");
 
  181                    $proxyToServerUnconnectedSocket->writePacket($buffer);
 
  183                    if($mostRecentPong !== 
null){
 
  184                        $clientProxySocket->
writePacket($mostRecentPong, $recvAddr, $recvPort);
 
  186                        \GlobalLogger::get()->info(
"No cached ping response, waiting for server to respond");
 
  188                }elseif(ord($buffer[0]) === MessageIdentifiers::ID_OPEN_CONNECTION_REQUEST_1){
 
  189                    \GlobalLogger::get()->info(
"Got connection from $recvAddr on port $recvPort");
 
  190                    $proxyToServerUnconnectedSocket->writePacket($buffer);
 
  192                    $client->getSocket()->setBlocking(
false);
 
  193                    $clients[$recvAddr][$recvPort] = $client;
 
  194                    socket_getsockname($client->getSocket()->getSocket(), $proxyAddr, $proxyPort);
 
  195                    \GlobalLogger::get()->info(
"Established connection: $recvAddr:$recvPort <-> $proxyAddr:$proxyPort <-> $serverAddress:$serverPort");
 
  197                    \GlobalLogger::get()->warning(
"Unexpected packet from unconnected client $recvAddr on port $recvPort: " . bin2hex($buffer));
 
  200                throw new \LogicException(
"Unexpected socket in select result");
 
  204    foreach($clients as $ip => $ipClients){
 
  205        foreach($ipClients as $port => $client){
 
  206            if(!$client->isActive()){
 
  207                \GlobalLogger::get()->info(
"Closing session for client $ip:$port");
 
  208                unset($clients[$ip][$port]);
 
readPacket(?string &$source, ?int &$port)
 
writePacket(string $buffer, string $dest, int $port)