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 if(isset($clients[$recvAddr][$recvPort])){
174 $client = $clients[$recvAddr][$recvPort];
175 $client->setActive();
176 $client->getSocket()->writePacket($buffer);
177 }elseif(ord($buffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PING){
178 \GlobalLogger::get()->info(
"Got ping from $recvAddr on port $recvPort, pinging server");
179 $proxyToServerUnconnectedSocket->writePacket($buffer);
181 if($mostRecentPong !==
null){
182 $clientProxySocket->
writePacket($mostRecentPong, $recvAddr, $recvPort);
184 \GlobalLogger::get()->info(
"No cached ping response, waiting for server to respond");
186 }elseif(ord($buffer[0]) === MessageIdentifiers::ID_OPEN_CONNECTION_REQUEST_1){
187 \GlobalLogger::get()->info(
"Got connection from $recvAddr on port $recvPort");
188 $proxyToServerUnconnectedSocket->writePacket($buffer);
190 $client->getSocket()->setBlocking(
false);
191 $clients[$recvAddr][$recvPort] = $client;
192 socket_getsockname($client->getSocket()->getSocket(), $proxyAddr, $proxyPort);
193 \GlobalLogger::get()->info(
"Established connection: $recvAddr:$recvPort <-> $proxyAddr:$proxyPort <-> $serverAddress:$serverPort");
195 \GlobalLogger::get()->warning(
"Unexpected packet from unconnected client $recvAddr on port $recvPort: " . bin2hex($buffer));
198 throw new \LogicException(
"Unexpected socket in select result");
202 foreach($clients as $ip => $ipClients){
203 foreach($ipClients as $port => $client){
204 if(!$client->isActive()){
205 \GlobalLogger::get()->info(
"Closing session for client $ip:$port");
206 unset($clients[$ip][$port]);
readPacket(?string &$source, ?int &$port)
writePacket(string $buffer, string $dest, int $port)