PocketMine-MP 5.18.2 git-00e39821f06a4b6d728d35053c2621dbb19369ff
proxy.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20*/
21
22declare(strict_types=1);
23
35
36require dirname(__DIR__) . '/vendor/autoload.php';
37
38$bindAddr = "0.0.0.0";
39$bindPort = 19132;
40
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){ //ctrl+c or ctrl+d
48 exit(1);
49 }
50 $serverAddress = trim($serverAddress);
51 echo "Enter server port: ";
52 $input = fgets(STDIN);
53 if($input === false){ //ctrl+c or ctrl+d
54 exit(1);
55 }
56 $serverPort = (int) trim($input);
57}else{
58 echo "Usage: php proxy.php [bind address] [bind port]\n";
59 exit(1);
60}
61
62$serverAddress = gethostbyname($serverAddress);
63
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");
66}
67
68\GlobalLogger::get()->info("Opening listen socket");
69try{
70 $proxyToServerUnconnectedSocket = new ClientSocket(new InternetAddress($serverAddress, $serverPort, 4));
71 $proxyToServerUnconnectedSocket->setBlocking(false);
72}catch(SocketException $e){
73 \GlobalLogger::get()->emergency("Can't connect to $serverAddress on port $serverPort, is the server online?");
74 \GlobalLogger::get()->emergency($e->getMessage());
75 exit(1);
76}
77socket_getsockname($proxyToServerUnconnectedSocket->getSocket(), $proxyAddr, $proxyPort);
78\GlobalLogger::get()->info("Listening on $bindAddr:$bindPort, sending from $proxyAddr:$proxyPort, sending to $serverAddress:$serverPort");
79
80try{
81 $clientProxySocket = new ServerSocket(new InternetAddress($bindAddr, $bindPort, 4));
82 $clientProxySocket->setBlocking(false);
83}catch(SocketException $e){
84 \GlobalLogger::get()->emergency("Can't bind to $bindAddr on port $bindPort, is something already using that port?");
85 \GlobalLogger::get()->emergency($e->getMessage());
86 exit(1);
87}
88
89\GlobalLogger::get()->info("Press CTRL+C to stop the proxy");
90
91$clientAddr = $clientPort = null;
92
94 private int $lastUsedTime;
95
96 public function __construct(
97 private InternetAddress $address,
98 private ClientSocket $proxyToServerSocket
99 ){
100 $this->lastUsedTime = time();
101 }
102
103 public function getAddress() : InternetAddress{
104 return $this->address;
105 }
106
107 public function getSocket() : ClientSocket{
108 return $this->proxyToServerSocket;
109 }
110
111 public function setActive() : void{
112 $this->lastUsedTime = time();
113 }
114
115 public function isActive() : bool{
116 return time() - $this->lastUsedTime < 10;
117 }
118}
119
120function serverToClientRelay(ClientSession $client, ServerSocket $clientProxySocket) : void{
121 $buffer = $client->getSocket()->readPacket();
122 if($buffer !== null){
123 $clientProxySocket->writePacket($buffer, $client->getAddress()->getIp(), $client->getAddress()->getPort());
124 }
125}
126
128$clients = [];
129
130$serverId = mt_rand(0, Limits::INT32_MAX);
131$mostRecentPong = null;
132
133while(true){
134 $k = 0;
135 $r = [];
136 $r[++$k] = $clientProxySocket->getSocket();
137 $r[++$k] = $proxyToServerUnconnectedSocket->getSocket();
138 $clientIndex = [];
139 foreach($clients as $ipClients){
140 foreach($ipClients as $client){
141 $key = ++$k;
142 $r[$key] = $client->getSocket()->getSocket();
143 $clientIndex[$key] = $client;
144 }
145 }
146 $w = $e = null;
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);
156 }
157 }elseif($socket === $clientProxySocket->getSocket()){
158 try{
159 $buffer = $clientProxySocket->readPacket($recvAddr, $recvPort);
160 }catch(SocketException $e){
161 $error = $e->getCode();
162 if($error === SOCKET_ECONNRESET){ //client disconnected improperly, maybe crash or lost connection
163 continue;
164 }
165
166 \GlobalLogger::get()->error("Socket error: " . $e->getMessage());
167 continue;
168 }
169
170 if($buffer === null || $buffer === ""){
171 continue;
172 }
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);
180
181 if($mostRecentPong !== null){
182 $clientProxySocket->writePacket($mostRecentPong, $recvAddr, $recvPort);
183 }else{
184 \GlobalLogger::get()->info("No cached ping response, waiting for server to respond");
185 }
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);
189 $client = new ClientSession(new InternetAddress($recvAddr, $recvPort, 4), new ClientSocket(new InternetAddress($serverAddress, $serverPort, 4)));
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");
194 }else{
195 \GlobalLogger::get()->warning("Unexpected packet from unconnected client $recvAddr on port $recvPort: " . bin2hex($buffer));
196 }
197 }else{
198 throw new \LogicException("Unexpected socket in select result");
199 }
200 }
201 }
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]);
207 }
208 }
209 }
210}
readPacket(?string &$source, ?int &$port)
writePacket(string $buffer, string $dest, int $port)