diff --git a/src/PocketMine.php b/src/PocketMine.php index 551585bbb..e251fd614 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -34,6 +34,7 @@ namespace pocketmine { use pocketmine\utils\Timezone; use pocketmine\wizard\SetupWizard; use Webmozart\PathUtil\Path; + use function defined; use function extension_loaded; use function phpversion; use function preg_match; @@ -145,6 +146,10 @@ namespace pocketmine { $messages[] = "The native PocketMine extension is no longer supported."; } + if(!defined('AF_INET6')){ + $messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support."; + } + return $messages; } diff --git a/src/Server.php b/src/Server.php index 731860d4a..b384c2957 100644 --- a/src/Server.php +++ b/src/Server.php @@ -321,6 +321,10 @@ class Server{ return $this->configGroup->getConfigInt("server-port", 19132); } + public function getPortV6() : int{ + return $this->configGroup->getConfigInt("server-portv6", 19133); + } + public function getViewDistance() : int{ return max(2, $this->configGroup->getConfigInt("view-distance", 8)); } @@ -337,6 +341,11 @@ class Server{ return $str !== "" ? $str : "0.0.0.0"; } + public function getIpV6() : string{ + $str = $this->configGroup->getConfigString("server-ipv6"); + return $str !== "" ? $str : "::"; + } + public function getServerUniqueId() : UuidInterface{ return $this->serverID; } @@ -784,6 +793,8 @@ class Server{ new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [ "motd" => VersionInfo::NAME . " Server", "server-port" => 19132, + "server-portv6" => 19133, + "enable-ipv6" => true, "white-list" => false, "max-players" => 20, "gamemode" => 0, @@ -1114,25 +1125,42 @@ class Server{ return true; } - private function startupPrepareNetworkInterfaces() : bool{ - $useQuery = $this->configGroup->getConfigBool("enable-query", true); - + private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery) : bool{ + $prettyIp = $ipV6 ? "[$ip]" : $ip; try{ - $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this)); + $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6)); }catch(NetworkInterfaceStartException $e){ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( - $this->getIp(), - (string) $this->getPort(), + $ip, + (string) $port, $e->getMessage() ))); return false; } - if(!$rakLibRegistered && $useQuery){ - //RakLib would normally handle the transport for Query packets - //if it's not registered we need to make sure Query still works - $this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface"))); + $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port))); + if($useQuery){ + if(!$rakLibRegistered){ + //RakLib would normally handle the transport for Query packets + //if it's not registered we need to make sure Query still works + $this->network->registerInterface(new DedicatedQueryNetworkInterface($ip, $port, $ipV6, new \PrefixedLogger($this->logger, "Dedicated Query Interface"))); + } + $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port))); + } + return true; + } + + private function startupPrepareNetworkInterfaces() : bool{ + $useQuery = $this->configGroup->getConfigBool("enable-query", true); + + if( + !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery) || + ( + $this->configGroup->getConfigBool("enable-ipv6", true) && + !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery) + ) + ){ + return false; } - $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($this->getIp(), (string) $this->getPort()))); if($useQuery){ $this->network->registerRawPacketHandler(new QueryHandler($this)); diff --git a/src/command/defaults/BanIpCommand.php b/src/command/defaults/BanIpCommand.php index fca99eff6..d6e1584b9 100644 --- a/src/command/defaults/BanIpCommand.php +++ b/src/command/defaults/BanIpCommand.php @@ -32,7 +32,7 @@ use pocketmine\player\Player; use function array_shift; use function count; use function implode; -use function preg_match; +use function inet_pton; class BanIpCommand extends VanillaCommand{ @@ -57,7 +57,7 @@ class BanIpCommand extends VanillaCommand{ $value = array_shift($args); $reason = implode(" ", $args); - if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $value)){ + if(inet_pton($value) !== false){ $this->processIPBan($value, $sender, $reason); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success($value)); diff --git a/src/command/defaults/PardonIpCommand.php b/src/command/defaults/PardonIpCommand.php index 395ba1d93..f0b2c9aa5 100644 --- a/src/command/defaults/PardonIpCommand.php +++ b/src/command/defaults/PardonIpCommand.php @@ -29,7 +29,7 @@ use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use function count; -use function preg_match; +use function inet_pton; class PardonIpCommand extends VanillaCommand{ @@ -52,7 +52,7 @@ class PardonIpCommand extends VanillaCommand{ throw new InvalidCommandSyntaxException(); } - if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $args[0])){ + if(inet_pton($args[0]) !== false){ $sender->getServer()->getIPBans()->remove($args[0]); $sender->getServer()->getNetwork()->unblockAddress($args[0]); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unbanip_success($args[0])); diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 4bfee2258..15a8177fb 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -88,7 +88,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ /** @var PacketBroadcaster */ private $broadcaster; - public function __construct(Server $server){ + public function __construct(Server $server, string $ip, int $port, bool $ipV6){ $this->server = $server; $this->rakServerId = mt_rand(0, PHP_INT_MAX); @@ -101,7 +101,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ $this->server->getLogger(), $mainToThreadBuffer, $threadToMainBuffer, - new InternetAddress($this->server->getIp(), $this->server->getPort(), 4), + new InternetAddress($ip, $port, $ipV6 ? 6 : 4), $this->rakServerId, $this->server->getConfigGroup()->getPropertyInt("network.max-mtu-size", 1492), self::MCPE_RAKNET_PROTOCOL_VERSION, diff --git a/src/network/query/DedicatedQueryNetworkInterface.php b/src/network/query/DedicatedQueryNetworkInterface.php index d742c9e53..70a021a5a 100644 --- a/src/network/query/DedicatedQueryNetworkInterface.php +++ b/src/network/query/DedicatedQueryNetworkInterface.php @@ -34,11 +34,14 @@ use function socket_recvfrom; use function socket_select; use function socket_sendto; use function socket_set_nonblock; +use function socket_set_option; use function socket_strerror; use function strlen; use function time; use function trim; use const AF_INET; +use const IPPROTO_IPV6; +use const IPV6_V6ONLY; use const PHP_INT_MAX; use const SOCK_DGRAM; use const SOCKET_EADDRINUSE; @@ -74,15 +77,18 @@ final class DedicatedQueryNetworkInterface implements AdvancedNetworkInterface{ /** @var string[] */ private $rawPacketPatterns = []; - public function __construct(string $ip, int $port, \Logger $logger){ + public function __construct(string $ip, int $port, bool $ipV6, \Logger $logger){ $this->ip = $ip; $this->port = $port; $this->logger = $logger; - $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + $socket = @socket_create($ipV6 ? AF_INET6 : AF_INET, SOCK_DGRAM, SOL_UDP); if($socket === false){ throw new \RuntimeException("Failed to create socket"); } + if($ipV6){ + socket_set_option($socket, IPPROTO_IPV6, IPV6_V6ONLY, 1); //disable linux's cool but annoying ipv4-over-ipv6 network stack + } $this->socket = $socket; } diff --git a/src/network/query/QueryHandler.php b/src/network/query/QueryHandler.php index 1ec8a2277..1fe207969 100644 --- a/src/network/query/QueryHandler.php +++ b/src/network/query/QueryHandler.php @@ -27,7 +27,6 @@ declare(strict_types=1); */ namespace pocketmine\network\query; -use pocketmine\lang\KnownTranslationFactory; use pocketmine\network\AdvancedNetworkInterface; use pocketmine\network\RawPacketHandler; use pocketmine\Server; @@ -57,8 +56,6 @@ class QueryHandler implements RawPacketHandler{ public function __construct(Server $server){ $this->server = $server; $this->logger = new \PrefixedLogger($this->server->getLogger(), "Query Handler"); - $addr = $this->server->getIp(); - $port = $this->server->getPort(); /* The Query protocol is built on top of the existing Minecraft PE UDP network stack. @@ -71,7 +68,6 @@ class QueryHandler implements RawPacketHandler{ $this->regenerateToken(); $this->lastToken = $this->token; - $this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($addr, (string) $port))); } public function getPattern() : string{