timeout time * @phpstan-var array */ private $blockedIps = []; /** @var string[] */ private $rawPacketPatterns = []; public function __construct(string $ip, int $port, \Logger $logger){ $this->ip = $ip; $this->port = $port; $this->logger = $logger; $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); if($socket === false){ throw new \RuntimeException("Failed to create socket"); } $this->socket = $socket; } public function start() : void{ if(!@socket_bind($this->socket, $this->ip, $this->port)){ $error = socket_last_error($this->socket); if($error === SOCKET_EADDRINUSE){ //platform error messages aren't consistent throw new \RuntimeException("Failed to bind socket: Something else is already running on $this->ip $this->port", $error); } throw new \RuntimeException("Failed to bind to $this->ip $this->port: " . trim(socket_strerror($error)), $error); } socket_set_nonblock($this->socket); $this->logger->info("Running on $this->ip $this->port"); } public function setName(string $name) : void{ //NOOP } public function tick() : void{ $r = [$this->socket]; $w = null; $e = null; if(@socket_select($r, $w, $e, 0, 0) === 1){ $address = ""; $port = 0; $buffer = ""; while(true){ $bytes = @socket_recvfrom($this->socket, $buffer, 65535, 0, $address, $port); if($bytes !== false){ if(isset($this->blockedIps[$address]) && $this->blockedIps[$address] > time()){ $this->logger->debug("Dropped packet from banned address $address"); continue; } foreach($this->rawPacketPatterns as $pattern){ if(preg_match($pattern, $buffer) === 1){ $this->network->processRawPacket($this, $address, $port, $buffer); break; } } }else{ $errno = socket_last_error($this->socket); if($errno === SOCKET_EWOULDBLOCK){ break; } if($errno !== SOCKET_ECONNRESET){ //remote peer disappeared unexpectedly, this might spam like crazy so we don't log it $this->logger->debug("Failed to recv (errno $errno): " . trim(socket_strerror($errno))); } } } } } public function blockAddress(string $address, int $timeout = 300) : void{ $this->blockedIps[$address] = $timeout > 0 ? time() + $timeout : PHP_INT_MAX; } public function unblockAddress(string $address) : void{ unset($this->blockedIps[$address]); } public function setNetwork(Network $network) : void{ $this->network = $network; } public function sendRawPacket(string $address, int $port, string $payload) : void{ if(@socket_sendto($this->socket, $payload, strlen($payload), 0, $address, $port) === false){ $errno = socket_last_error($this->socket); throw new \RuntimeException("Failed to send to $address $port (errno $errno): " . trim(socket_strerror($errno)), $errno); } } public function addRawPacketFilter(string $regex) : void{ $this->rawPacketPatterns[] = $regex; } public function shutdown() : void{ @socket_close($this->socket); } }