22declare(strict_types=1);
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;
44use
const IPPROTO_IPV6;
48use
const SOCKET_EADDRINUSE;
49use
const SOCKET_ECONNRESET;
50use
const SOCKET_EWOULDBLOCK;
63 private \Socket $socket;
69 private array $blockedIps = [];
71 private array $rawPacketPatterns = [];
73 public function __construct(
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");
84 socket_set_option($socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
86 $this->socket = $socket;
90 if(!@socket_bind($this->socket, $this->ip, $this->port)){
91 $error = socket_last_error($this->socket);
92 if($error === SOCKET_EADDRINUSE){
93 throw new \RuntimeException(
"Failed to bind socket: Something else is already running on $this->ip $this->port", $error);
95 throw new \RuntimeException(
"Failed to bind to $this->ip $this->port: " . trim(socket_strerror($error)), $error);
97 socket_set_nonblock($this->socket);
98 $this->logger->info(
"Running on $this->ip $this->port");
101 public function setName(
string $name) : void{
105 public function tick() : void{
106 $r = [$this->socket];
109 if(@socket_select($r, $w, $e, 0, 0) === 1){
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");
120 foreach($this->rawPacketPatterns as $pattern){
121 if(preg_match($pattern, $buffer) === 1){
122 $this->network->processRawPacket($this, $address, $port, $buffer);
127 $errno = socket_last_error($this->socket);
128 if($errno === SOCKET_EWOULDBLOCK){
131 if($errno !== SOCKET_ECONNRESET){
132 $this->logger->debug(
"Failed to recv (errno $errno): " . trim(socket_strerror($errno)));
139 public function blockAddress(
string $address,
int $timeout = 300) : void{
140 $this->blockedIps[$address] = $timeout > 0 ? time() + $timeout : PHP_INT_MAX;
144 unset($this->blockedIps[$address]);
147 public function setNetwork(
Network $network) : void{
148 $this->network = $network;
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);
159 $this->rawPacketPatterns[] = $regex;
163 @socket_close($this->socket);
blockAddress(string $address, int $timeout=300)
unblockAddress(string $address)
sendRawPacket(string $address, int $port, string $payload)
addRawPacketFilter(string $regex)