22declare(strict_types=1);
36require dirname(__DIR__) .
'/vendor/autoload.php';
42if(count($argv) === 3){
43 $serverAddress = $argv[1];
44 $serverPort = (int) $argv[2];
45}elseif(count($argv) === 1){
46 echo
"Enter server address: ";
47 $serverAddress = fgets(STDIN);
48 if($serverAddress ===
false){
51 $serverAddress = trim($serverAddress);
52 echo
"Enter server port: ";
53 $input = fgets(STDIN);
57 $serverPort = (int) trim($input);
59 echo
"Usage: php proxy.php [bind address] [bind port]\n";
63$serverAddress = gethostbyname($serverAddress);
65if($serverPort !== 19132){
66 \GlobalLogger::get()->warning(
"You may experience problems connecting to PocketMine-MP servers on ports other than 19132 if the server has port checking enabled");
69\GlobalLogger::get()->info(
"Opening listen socket");
72 $proxyToServerUnconnectedSocket->setBlocking(
false);
74 \GlobalLogger::get()->emergency(
"Can't connect to $serverAddress on port $serverPort, is the server online?");
75 \GlobalLogger::get()->emergency($e->getMessage());
78socket_getsockname($proxyToServerUnconnectedSocket->getSocket(), $proxyAddr, $proxyPort);
79\GlobalLogger::get()->info(
"Listening on $bindAddr:$bindPort, sending from $proxyAddr:$proxyPort, sending to $serverAddress:$serverPort");
83 $clientProxySocket->setBlocking(
false);
85 \GlobalLogger::get()->emergency(
"Can't bind to $bindAddr on port $bindPort, is something already using that port?");
86 \GlobalLogger::get()->emergency($e->getMessage());
90\GlobalLogger::get()->info(
"Press CTRL+C to stop the proxy");
92$clientAddr = $clientPort =
null;
95 private int $lastUsedTime;
97 public function __construct(
101 $this->lastUsedTime = time();
105 return $this->address;
109 return $this->proxyToServerSocket;
112 public function setActive() :
void{
113 $this->lastUsedTime = time();
116 public function isActive() :
bool{
117 return time() - $this->lastUsedTime < 10;
122 $buffer = $client->getSocket()->readPacket();
123 if($buffer !==
null){
124 $clientProxySocket->
writePacket($buffer, $client->getAddress()->getIp(), $client->getAddress()->getPort());
131$serverId = mt_rand(0, Limits::INT32_MAX);
132$mostRecentPong =
null;
137 $r[++$k] = $clientProxySocket->getSocket();
138 $r[++$k] = $proxyToServerUnconnectedSocket->getSocket();
140 foreach($clients as $ipClients){
141 foreach($ipClients as $client){
143 $r[$key] = $client->getSocket()->getSocket();
144 $clientIndex[$key] = $client;
148 if(socket_select($r, $w, $e, 10) > 0){
149 foreach($r as $key => $socket){
150 if(isset($clientIndex[$key])){
151 serverToClientRelay($clientIndex[$key], $clientProxySocket);
152 }elseif($socket === $proxyToServerUnconnectedSocket->getSocket()){
153 $buffer = $proxyToServerUnconnectedSocket->readPacket();
154 if($buffer !==
null && $buffer !==
"" && ord($buffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PONG){
155 $mostRecentPong = $buffer;
156 \GlobalLogger::get()->info(
"Caching ping response from server: " . preg_replace(
"/[[:^print:]]/",
".", $buffer));
158 }elseif($socket === $clientProxySocket->getSocket()){
160 $buffer = $clientProxySocket->
readPacket($recvAddr, $recvPort);
162 $error = $e->getCode();
163 if($error === SOCKET_ECONNRESET){
167 \GlobalLogger::get()->error(
"Socket error: " . $e->getMessage());
171 if($buffer ===
null || $buffer ===
""){
174 assert($recvAddr !==
null,
"Can't be null if we got a buffer");
175 assert($recvPort !==
null,
"Can't be null if we got a buffer");
176 if(isset($clients[$recvAddr][$recvPort])){
177 $client = $clients[$recvAddr][$recvPort];
178 $client->setActive();
179 $client->getSocket()->writePacket($buffer);
180 }elseif(ord($buffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PING){
181 \GlobalLogger::get()->info(
"Got ping from $recvAddr on port $recvPort, pinging server");
182 $proxyToServerUnconnectedSocket->writePacket($buffer);
184 if($mostRecentPong !==
null){
185 $clientProxySocket->
writePacket($mostRecentPong, $recvAddr, $recvPort);
187 \GlobalLogger::get()->info(
"No cached ping response, waiting for server to respond");
189 }elseif(ord($buffer[0]) === MessageIdentifiers::ID_OPEN_CONNECTION_REQUEST_1){
190 \GlobalLogger::get()->info(
"Got connection from $recvAddr on port $recvPort");
191 $proxyToServerUnconnectedSocket->writePacket($buffer);
193 $client->getSocket()->setBlocking(
false);
194 $clients[$recvAddr][$recvPort] = $client;
195 socket_getsockname($client->getSocket()->getSocket(), $proxyAddr, $proxyPort);
196 \GlobalLogger::get()->info(
"Established connection: $recvAddr:$recvPort <-> $proxyAddr:$proxyPort <-> $serverAddress:$serverPort");
198 \GlobalLogger::get()->warning(
"Unexpected packet from unconnected client $recvAddr on port $recvPort: " . bin2hex($buffer));
201 throw new \LogicException(
"Unexpected socket in select result");
205 foreach($clients as $ip => $ipClients){
206 foreach($ipClients as $port => $client){
207 if(!$client->isActive()){
208 \GlobalLogger::get()->info(
"Closing session for client $ip:$port");
209 unset($clients[$ip][$port]);
readPacket(?string &$source, ?int &$port)
writePacket(string $buffer, string $dest, int $port)