PocketMine-MP 5.39.3 git-66148f13a91e4af6778ba9f200ca25ad8a04a584
Loading...
Searching...
No Matches
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
41$argv ??= [];
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){ //ctrl+c or ctrl+d
49 exit(1);
50 }
51 $serverAddress = trim($serverAddress);
52 echo "Enter server port: ";
53 $input = fgets(STDIN);
54 if($input === false){ //ctrl+c or ctrl+d
55 exit(1);
56 }
57 $serverPort = (int) trim($input);
58}else{
59 echo "Usage: php proxy.php [bind address] [bind port]\n";
60 exit(1);
61}
62
63$serverAddress = gethostbyname($serverAddress);
64
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");
67}
68
69\GlobalLogger::get()->info("Opening listen socket");
70try{
71 $proxyToServerUnconnectedSocket = new ClientSocket(new InternetAddress($serverAddress, $serverPort, 4));
72 $proxyToServerUnconnectedSocket->setBlocking(false);
73}catch(SocketException $e){
74 \GlobalLogger::get()->emergency("Can't connect to $serverAddress on port $serverPort, is the server online?");
75 \GlobalLogger::get()->emergency($e->getMessage());
76 exit(1);
77}
78socket_getsockname($proxyToServerUnconnectedSocket->getSocket(), $proxyAddr, $proxyPort);
79\GlobalLogger::get()->info("Listening on $bindAddr:$bindPort, sending from $proxyAddr:$proxyPort, sending to $serverAddress:$serverPort");
80
81try{
82 $clientProxySocket = new ServerSocket(new InternetAddress($bindAddr, $bindPort, 4));
83 $clientProxySocket->setBlocking(false);
84}catch(SocketException $e){
85 \GlobalLogger::get()->emergency("Can't bind to $bindAddr on port $bindPort, is something already using that port?");
86 \GlobalLogger::get()->emergency($e->getMessage());
87 exit(1);
88}
89
90\GlobalLogger::get()->info("Press CTRL+C to stop the proxy");
91
92$clientAddr = $clientPort = null;
93
95 private int $lastUsedTime;
96
97 public function __construct(
98 private InternetAddress $address,
99 private ClientSocket $proxyToServerSocket
100 ){
101 $this->lastUsedTime = time();
102 }
103
104 public function getAddress() : InternetAddress{
105 return $this->address;
106 }
107
108 public function getSocket() : ClientSocket{
109 return $this->proxyToServerSocket;
110 }
111
112 public function setActive() : void{
113 $this->lastUsedTime = time();
114 }
115
116 public function isActive() : bool{
117 return time() - $this->lastUsedTime < 10;
118 }
119}
120
121function serverToClientRelay(ClientSession $client, ServerSocket $clientProxySocket) : void{
122 $buffer = $client->getSocket()->readPacket();
123 if($buffer !== null){
124 $clientProxySocket->writePacket($buffer, $client->getAddress()->getIp(), $client->getAddress()->getPort());
125 }
126}
127
129$clients = [];
130
131$serverId = mt_rand(0, Limits::INT32_MAX);
132$mostRecentPong = null;
133
134while(true){
135 $k = 0;
136 $r = [];
137 $r[++$k] = $clientProxySocket->getSocket();
138 $r[++$k] = $proxyToServerUnconnectedSocket->getSocket();
139 $clientIndex = [];
140 foreach($clients as $ipClients){
141 foreach($ipClients as $client){
142 $key = ++$k;
143 $r[$key] = $client->getSocket()->getSocket();
144 $clientIndex[$key] = $client;
145 }
146 }
147 $w = $e = null;
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));
157 }
158 }elseif($socket === $clientProxySocket->getSocket()){
159 try{
160 $buffer = $clientProxySocket->readPacket($recvAddr, $recvPort);
161 }catch(SocketException $e){
162 $error = $e->getCode();
163 if($error === SOCKET_ECONNRESET){ //client disconnected improperly, maybe crash or lost connection
164 continue;
165 }
166
167 \GlobalLogger::get()->error("Socket error: " . $e->getMessage());
168 continue;
169 }
170
171 if($buffer === null || $buffer === ""){
172 continue;
173 }
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);
183
184 if($mostRecentPong !== null){
185 $clientProxySocket->writePacket($mostRecentPong, $recvAddr, $recvPort);
186 }else{
187 \GlobalLogger::get()->info("No cached ping response, waiting for server to respond");
188 }
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);
192 $client = new ClientSession(new InternetAddress($recvAddr, $recvPort, 4), new ClientSocket(new InternetAddress($serverAddress, $serverPort, 4)));
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");
197 }else{
198 \GlobalLogger::get()->warning("Unexpected packet from unconnected client $recvAddr on port $recvPort: " . bin2hex($buffer));
199 }
200 }else{
201 throw new \LogicException("Unexpected socket in select result");
202 }
203 }
204 }
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]);
210 }
211 }
212 }
213}
readPacket(?string &$source, ?int &$port)
writePacket(string $buffer, string $dest, int $port)