PocketMine-MP 5.21.1 git-e598364f0695495cbe71ddf0b62f134b51091c6e
DedicatedQueryNetworkInterface.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
25
28use function preg_match;
29use function socket_bind;
30use function socket_close;
31use function socket_create;
32use function socket_last_error;
33use function socket_recvfrom;
34use function socket_select;
35use function socket_sendto;
36use function socket_set_nonblock;
37use function socket_set_option;
38use function socket_strerror;
39use function strlen;
40use function time;
41use function trim;
42use const AF_INET;
43use const AF_INET6;
44use const IPPROTO_IPV6;
45use const IPV6_V6ONLY;
46use const PHP_INT_MAX;
47use const SOCK_DGRAM;
48use const SOCKET_EADDRINUSE;
49use const SOCKET_ECONNRESET;
50use const SOCKET_EWOULDBLOCK;
51use const SOL_UDP;
52
63 private \Socket $socket;
64 private Network $network;
69 private array $blockedIps = [];
71 private array $rawPacketPatterns = [];
72
73 public function __construct(
74 private string $ip,
75 private int $port,
76 bool $ipV6,
77 private \Logger $logger
78 ){
79 $socket = @socket_create($ipV6 ? AF_INET6 : AF_INET, SOCK_DGRAM, SOL_UDP);
80 if($socket === false){
81 throw new \RuntimeException("Failed to create socket");
82 }
83 if($ipV6){
84 socket_set_option($socket, IPPROTO_IPV6, IPV6_V6ONLY, 1); //disable linux's cool but annoying ipv4-over-ipv6 network stack
85 }
86 $this->socket = $socket;
87 }
88
89 public function start() : void{
90 if(!@socket_bind($this->socket, $this->ip, $this->port)){
91 $error = socket_last_error($this->socket);
92 if($error === SOCKET_EADDRINUSE){ //platform error messages aren't consistent
93 throw new \RuntimeException("Failed to bind socket: Something else is already running on $this->ip $this->port", $error);
94 }
95 throw new \RuntimeException("Failed to bind to $this->ip $this->port: " . trim(socket_strerror($error)), $error);
96 }
97 socket_set_nonblock($this->socket);
98 $this->logger->info("Running on $this->ip $this->port");
99 }
100
101 public function setName(string $name) : void{
102 //NOOP
103 }
104
105 public function tick() : void{
106 $r = [$this->socket];
107 $w = null;
108 $e = null;
109 if(@socket_select($r, $w, $e, 0, 0) === 1){
110 $address = "";
111 $port = 0;
112 $buffer = "";
113 while(true){
114 $bytes = @socket_recvfrom($this->socket, $buffer, 65535, 0, $address, $port);
115 if($bytes !== false){
116 if(isset($this->blockedIps[$address]) && $this->blockedIps[$address] > time()){
117 $this->logger->debug("Dropped packet from banned address $address");
118 continue;
119 }
120 foreach($this->rawPacketPatterns as $pattern){
121 if(preg_match($pattern, $buffer) === 1){
122 $this->network->processRawPacket($this, $address, $port, $buffer);
123 break;
124 }
125 }
126 }else{
127 $errno = socket_last_error($this->socket);
128 if($errno === SOCKET_EWOULDBLOCK){
129 break;
130 }
131 if($errno !== SOCKET_ECONNRESET){ //remote peer disappeared unexpectedly, this might spam like crazy so we don't log it
132 $this->logger->debug("Failed to recv (errno $errno): " . trim(socket_strerror($errno)));
133 }
134 }
135 }
136 }
137 }
138
139 public function blockAddress(string $address, int $timeout = 300) : void{
140 $this->blockedIps[$address] = $timeout > 0 ? time() + $timeout : PHP_INT_MAX;
141 }
142
143 public function unblockAddress(string $address) : void{
144 unset($this->blockedIps[$address]);
145 }
146
147 public function setNetwork(Network $network) : void{
148 $this->network = $network;
149 }
150
151 public function sendRawPacket(string $address, int $port, string $payload) : void{
152 if(@socket_sendto($this->socket, $payload, strlen($payload), 0, $address, $port) === false){
153 $errno = socket_last_error($this->socket);
154 throw new \RuntimeException("Failed to send to $address $port (errno $errno): " . trim(socket_strerror($errno)), $errno);
155 }
156 }
157
158 public function addRawPacketFilter(string $regex) : void{
159 $this->rawPacketPatterns[] = $regex;
160 }
161
162 public function shutdown() : void{
163 @socket_close($this->socket);
164 }
165}