2022-10-30 00:23:21 +00:00
/ *
* Copyright ( c ) 2019 - 2022 GeyserMC . http : //geysermc.org
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*
* @author GeyserMC
* @link https : //github.com/GeyserMC/Geyser
* /
package org.geysermc.geyser.network.netty ;
2022-10-30 22:00:08 +00:00
import com.github.steveice10.packetlib.helper.TransportHelper ;
2022-10-30 00:23:21 +00:00
import io.netty.bootstrap.ServerBootstrap ;
2023-04-09 00:44:01 +00:00
import io.netty.channel.Channel ;
2022-10-30 00:23:21 +00:00
import io.netty.channel.ChannelFuture ;
import io.netty.channel.EventLoopGroup ;
2022-10-30 22:00:08 +00:00
import io.netty.channel.epoll.Epoll ;
import io.netty.channel.epoll.EpollDatagramChannel ;
import io.netty.channel.epoll.EpollEventLoopGroup ;
import io.netty.channel.kqueue.KQueue ;
import io.netty.channel.kqueue.KQueueDatagramChannel ;
import io.netty.channel.kqueue.KQueueEventLoopGroup ;
import io.netty.channel.nio.NioEventLoopGroup ;
import io.netty.channel.socket.DatagramChannel ;
2022-10-30 00:23:21 +00:00
import io.netty.channel.socket.nio.NioDatagramChannel ;
2023-10-01 22:15:44 +00:00
import io.netty.util.concurrent.Future ;
2023-05-02 17:17:25 +00:00
import lombok.Getter ;
import net.jodah.expiringmap.ExpirationPolicy ;
import net.jodah.expiringmap.ExpiringMap ;
2022-10-30 00:23:21 +00:00
import org.cloudburstmc.netty.channel.raknet.RakChannelFactory ;
2022-10-30 03:02:11 +00:00
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption ;
2022-10-30 22:00:08 +00:00
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler ;
2022-10-30 03:02:11 +00:00
import org.cloudburstmc.protocol.bedrock.BedrockPong ;
2022-10-30 00:23:21 +00:00
import org.geysermc.geyser.GeyserImpl ;
2023-09-06 03:16:44 +00:00
import org.geysermc.geyser.command.defaults.ConnectionTestCommand ;
2022-10-30 22:00:08 +00:00
import org.geysermc.geyser.configuration.GeyserConfiguration ;
2023-05-10 03:50:38 +00:00
import org.geysermc.geyser.event.type.GeyserBedrockPingEventImpl ;
2023-04-09 00:01:42 +00:00
import org.geysermc.geyser.network.CIDRMatcher ;
2022-10-30 03:02:11 +00:00
import org.geysermc.geyser.network.GameProtocol ;
2022-10-30 00:23:21 +00:00
import org.geysermc.geyser.network.GeyserServerInitializer ;
2023-04-09 00:44:01 +00:00
import org.geysermc.geyser.network.netty.handler.RakConnectionRequestHandler ;
import org.geysermc.geyser.network.netty.handler.RakPingHandler ;
2023-05-02 17:17:25 +00:00
import org.geysermc.geyser.network.netty.proxy.ProxyServerHandler ;
2022-10-30 22:00:08 +00:00
import org.geysermc.geyser.ping.GeyserPingInfo ;
import org.geysermc.geyser.ping.IGeyserPingPassthrough ;
2023-10-01 22:15:44 +00:00
import org.geysermc.geyser.skin.SkinProvider ;
2022-10-30 22:00:08 +00:00
import org.geysermc.geyser.text.GeyserLocale ;
import org.geysermc.geyser.translator.text.MessageTranslator ;
2022-10-30 00:23:21 +00:00
import java.net.InetSocketAddress ;
2022-10-30 22:00:08 +00:00
import java.nio.charset.StandardCharsets ;
2023-04-09 00:01:42 +00:00
import java.util.List ;
2023-04-07 01:47:37 +00:00
import java.util.concurrent.CompletableFuture ;
2023-05-02 17:17:25 +00:00
import java.util.concurrent.TimeUnit ;
2023-04-07 01:47:37 +00:00
import java.util.function.IntFunction ;
2023-11-13 23:17:40 +00:00
import java.util.function.Supplier ;
2022-10-30 00:23:21 +00:00
public final class GeyserServer {
2022-10-30 22:00:08 +00:00
private static final boolean PRINT_DEBUG_PINGS = Boolean . parseBoolean ( System . getProperty ( " Geyser.PrintPingsInDebugMode " , " true " ) ) ;
/ *
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
* /
private static final int MINECRAFT_VERSION_BYTES_LENGTH = GameProtocol . DEFAULT_BEDROCK_CODEC . getMinecraftVersion ( ) . getBytes ( StandardCharsets . UTF_8 ) . length ;
private static final int BRAND_BYTES_LENGTH = GeyserImpl . NAME . getBytes ( StandardCharsets . UTF_8 ) . length ;
/ * *
* The MOTD , sub - MOTD and Minecraft version ( { @link # MINECRAFT_VERSION_BYTES_LENGTH } ) combined cannot reach this length .
* /
private static final int MAGIC_RAKNET_LENGTH = 338 ;
private static final Transport TRANSPORT = compatibleTransport ( ) ;
2023-10-01 22:15:44 +00:00
/ * *
* See { @link EventLoopGroup # shutdownGracefully ( long , long , TimeUnit ) }
* /
private static final int SHUTDOWN_QUIET_PERIOD_MS = 100 ;
private static final int SHUTDOWN_TIMEOUT_MS = 500 ;
2022-10-30 00:23:21 +00:00
private final GeyserImpl geyser ;
2023-10-01 22:15:44 +00:00
private EventLoopGroup group ;
2022-10-30 00:23:21 +00:00
private final ServerBootstrap bootstrap ;
2023-10-01 22:15:44 +00:00
private EventLoopGroup playerGroup ;
2022-10-30 00:23:21 +00:00
2023-05-02 17:17:25 +00:00
@Getter
private final ExpiringMap < InetSocketAddress , InetSocketAddress > proxiedAddresses ;
2023-10-01 22:15:44 +00:00
private ChannelFuture bootstrapFuture ;
2022-10-30 00:23:21 +00:00
2024-01-24 21:28:03 +00:00
/ * *
* The port to broadcast in the pong . This can be different from the port the server is bound to , e . g . due to port forwarding .
* /
private final int broadcastPort ;
2022-10-30 00:23:21 +00:00
public GeyserServer ( GeyserImpl geyser , int threadCount ) {
this . geyser = geyser ;
2022-10-30 22:00:08 +00:00
this . group = TRANSPORT . eventLoopGroupFactory ( ) . apply ( threadCount ) ;
2022-10-30 00:23:21 +00:00
2022-10-30 22:00:08 +00:00
this . bootstrap = this . createBootstrap ( this . group ) ;
2023-05-02 17:17:25 +00:00
if ( this . geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) ) {
this . proxiedAddresses = ExpiringMap . builder ( )
. expiration ( 30 + 1 , TimeUnit . MINUTES )
. expirationPolicy ( ExpirationPolicy . ACCESSED ) . build ( ) ;
} else {
this . proxiedAddresses = null ;
}
2024-01-24 21:28:03 +00:00
// It's set to 0 only if no system property or manual config value was set
if ( geyser . getConfig ( ) . getBedrock ( ) . broadcastPort ( ) = = 0 ) {
geyser . getConfig ( ) . getBedrock ( ) . setBroadcastPort ( geyser . getConfig ( ) . getBedrock ( ) . port ( ) ) ;
}
this . broadcastPort = geyser . getConfig ( ) . getBedrock ( ) . broadcastPort ( ) ;
2022-10-30 00:23:21 +00:00
}
2023-04-07 01:47:37 +00:00
public CompletableFuture < Void > bind ( InetSocketAddress address ) {
CompletableFuture < Void > future = new CompletableFuture < > ( ) ;
2023-10-01 22:15:44 +00:00
this . bootstrapFuture = this . bootstrap . bind ( address ) . addListener ( bindResult - > {
2023-04-07 01:47:37 +00:00
if ( bindResult . cause ( ) ! = null ) {
future . completeExceptionally ( bindResult . cause ( ) ) ;
return ;
}
future . complete ( null ) ;
} ) ;
2022-10-30 22:00:08 +00:00
2023-10-01 22:15:44 +00:00
Channel channel = this . bootstrapFuture . channel ( ) ;
2023-04-09 00:44:01 +00:00
2022-10-30 22:00:08 +00:00
// Add our ping handler
2023-04-09 00:44:01 +00:00
channel . pipeline ( )
2023-04-09 00:01:42 +00:00
. addFirst ( RakConnectionRequestHandler . NAME , new RakConnectionRequestHandler ( this ) )
. addAfter ( RakServerOfflineHandler . NAME , RakPingHandler . NAME , new RakPingHandler ( this ) ) ;
2023-04-09 00:44:01 +00:00
if ( this . geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) ) {
2023-05-02 17:17:25 +00:00
channel . pipeline ( ) . addFirst ( " proxy-protocol-decoder " , new ProxyServerHandler ( ) ) ;
2023-04-09 00:44:01 +00:00
}
2023-04-07 01:47:37 +00:00
return future ;
2022-10-30 00:23:21 +00:00
}
public void shutdown ( ) {
2023-10-01 22:15:44 +00:00
try {
Future < ? > future1 = this . group . shutdownGracefully ( SHUTDOWN_QUIET_PERIOD_MS , SHUTDOWN_TIMEOUT_MS , TimeUnit . MILLISECONDS ) ;
this . group = null ;
Future < ? > future2 = this . playerGroup . shutdownGracefully ( SHUTDOWN_QUIET_PERIOD_MS , SHUTDOWN_TIMEOUT_MS , TimeUnit . MILLISECONDS ) ;
this . playerGroup = null ;
future1 . sync ( ) ;
future2 . sync ( ) ;
SkinProvider . shutdown ( ) ;
} catch ( InterruptedException e ) {
GeyserImpl . getInstance ( ) . getLogger ( ) . severe ( " Exception in shutdown process " , e ) ;
}
this . bootstrapFuture . channel ( ) . closeFuture ( ) . syncUninterruptibly ( ) ;
2022-10-30 00:23:21 +00:00
}
private ServerBootstrap createBootstrap ( EventLoopGroup group ) {
2022-10-30 22:00:08 +00:00
if ( this . geyser . getConfig ( ) . isDebugMode ( ) ) {
this . geyser . getLogger ( ) . debug ( " EventLoop type: " + TRANSPORT . datagramChannel ( ) ) ;
if ( TRANSPORT . datagramChannel ( ) = = NioDatagramChannel . class ) {
if ( System . getProperties ( ) . contains ( " disableNativeEventLoop " ) ) {
this . geyser . getLogger ( ) . debug ( " EventLoop type is NIO because native event loops are disabled. " ) ;
} else {
2023-11-13 23:17:40 +00:00
// Use lambda here, not method reference, or else NoClassDefFoundError for Epoll/KQueue will not be caught
this . geyser . getLogger ( ) . debug ( " Reason for no Epoll: " + throwableOrCaught ( ( ) - > Epoll . unavailabilityCause ( ) ) ) ;
this . geyser . getLogger ( ) . debug ( " Reason for no KQueue: " + throwableOrCaught ( ( ) - > KQueue . unavailabilityCause ( ) ) ) ;
2022-10-30 22:00:08 +00:00
}
}
}
2023-10-01 22:15:44 +00:00
GeyserServerInitializer serverInitializer = new GeyserServerInitializer ( this . geyser ) ;
playerGroup = serverInitializer . getEventLoopGroup ( ) ;
2023-10-20 18:14:16 +00:00
this . geyser . getLogger ( ) . debug ( " Setting MTU to " + this . geyser . getConfig ( ) . getMtu ( ) ) ;
2022-10-30 00:23:21 +00:00
return new ServerBootstrap ( )
2022-10-30 22:00:08 +00:00
. channelFactory ( RakChannelFactory . server ( TRANSPORT . datagramChannel ( ) ) )
2022-10-30 00:23:21 +00:00
. group ( group )
2022-10-30 22:00:08 +00:00
. option ( RakChannelOption . RAK_HANDLE_PING , true )
2023-10-20 18:14:16 +00:00
. option ( RakChannelOption . RAK_MAX_MTU , this . geyser . getConfig ( ) . getMtu ( ) )
2023-10-01 22:15:44 +00:00
. childHandler ( serverInitializer ) ;
2022-10-30 00:23:21 +00:00
}
2022-10-30 03:02:11 +00:00
2023-04-09 00:01:42 +00:00
public boolean onConnectionRequest ( InetSocketAddress inetSocketAddress ) {
List < String > allowedProxyIPs = geyser . getConfig ( ) . getBedrock ( ) . getProxyProtocolWhitelistedIPs ( ) ;
if ( geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) & & ! allowedProxyIPs . isEmpty ( ) ) {
boolean isWhitelistedIP = false ;
for ( CIDRMatcher matcher : geyser . getConfig ( ) . getBedrock ( ) . getWhitelistedIPsMatchers ( ) ) {
if ( matcher . matches ( inetSocketAddress . getAddress ( ) ) ) {
isWhitelistedIP = true ;
break ;
}
}
if ( ! isWhitelistedIP ) {
return false ;
}
}
2023-05-02 17:17:25 +00:00
String ip ;
if ( geyser . getConfig ( ) . isLogPlayerIpAddresses ( ) ) {
if ( geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) ) {
ip = this . proxiedAddresses . getOrDefault ( inetSocketAddress , inetSocketAddress ) . toString ( ) ;
} else {
ip = inetSocketAddress . toString ( ) ;
}
} else {
ip = " <IP address withheld> " ;
}
2023-04-09 00:01:42 +00:00
geyser . getLogger ( ) . info ( GeyserLocale . getLocaleStringLog ( " geyser.network.attempt_connect " , ip ) ) ;
return true ;
}
2022-10-30 22:00:08 +00:00
public BedrockPong onQuery ( InetSocketAddress inetSocketAddress ) {
if ( geyser . getConfig ( ) . isDebugMode ( ) & & PRINT_DEBUG_PINGS ) {
2023-06-20 13:28:31 +00:00
String ip ;
if ( geyser . getConfig ( ) . isLogPlayerIpAddresses ( ) ) {
if ( geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) ) {
ip = this . proxiedAddresses . getOrDefault ( inetSocketAddress , inetSocketAddress ) . toString ( ) ;
} else {
ip = inetSocketAddress . toString ( ) ;
}
} else {
ip = " <IP address withheld> " ;
}
2022-10-30 22:00:08 +00:00
geyser . getLogger ( ) . debug ( GeyserLocale . getLocaleStringLog ( " geyser.network.pinged " , ip ) ) ;
}
GeyserConfiguration config = geyser . getConfig ( ) ;
GeyserPingInfo pingInfo = null ;
if ( config . isPassthroughMotd ( ) | | config . isPassthroughPlayerCounts ( ) ) {
IGeyserPingPassthrough pingPassthrough = geyser . getBootstrap ( ) . getGeyserPingPassthrough ( ) ;
2023-11-13 23:17:40 +00:00
if ( pingPassthrough ! = null ) {
pingInfo = pingPassthrough . getPingInformation ( inetSocketAddress ) ;
}
2022-10-30 22:00:08 +00:00
}
BedrockPong pong = new BedrockPong ( )
2022-10-30 03:02:11 +00:00
. edition ( " MCPE " )
. gameType ( " Survival " ) // Can only be Survival or Creative as of 1.16.210.59
. nintendoLimited ( false )
. protocolVersion ( GameProtocol . DEFAULT_BEDROCK_CODEC . getProtocolVersion ( ) )
. version ( GameProtocol . DEFAULT_BEDROCK_CODEC . getMinecraftVersion ( ) ) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
2024-01-24 21:28:03 +00:00
. ipv4Port ( this . broadcastPort )
. ipv6Port ( this . broadcastPort )
2023-10-01 22:15:44 +00:00
. serverId ( bootstrapFuture . channel ( ) . config ( ) . getOption ( RakChannelOption . RAK_GUID ) ) ;
2022-10-30 22:00:08 +00:00
if ( config . isPassthroughMotd ( ) & & pingInfo ! = null & & pingInfo . getDescription ( ) ! = null ) {
String [ ] motd = MessageTranslator . convertMessageLenient ( pingInfo . getDescription ( ) ) . split ( " \ n " ) ;
2023-11-13 23:33:34 +00:00
String mainMotd = ( motd . length > 0 ) ? motd [ 0 ] : config . getBedrock ( ) . primaryMotd ( ) ; // First line of the motd.
String subMotd = ( motd . length > 1 ) ? motd [ 1 ] : config . getBedrock ( ) . secondaryMotd ( ) ; // Second line of the motd if present, otherwise default.
2022-10-30 22:00:08 +00:00
pong . motd ( mainMotd . trim ( ) ) ;
pong . subMotd ( subMotd . trim ( ) ) ; // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
} else {
pong . motd ( config . getBedrock ( ) . primaryMotd ( ) ) ;
pong . subMotd ( config . getBedrock ( ) . secondaryMotd ( ) ) ;
}
2023-05-10 03:50:38 +00:00
// Placed here to prevent overriding values set in the ping event.
if ( config . isPassthroughPlayerCounts ( ) & & pingInfo ! = null ) {
pong . playerCount ( pingInfo . getPlayers ( ) . getOnline ( ) ) ;
pong . maximumPlayerCount ( pingInfo . getPlayers ( ) . getMax ( ) ) ;
} else {
pong . playerCount ( geyser . getSessionManager ( ) . getSessions ( ) . size ( ) ) ;
pong . maximumPlayerCount ( config . getMaxPlayers ( ) ) ;
}
this . geyser . eventBus ( ) . fire ( new GeyserBedrockPingEventImpl ( pong , inetSocketAddress ) ) ;
2022-12-21 00:47:45 +00:00
// https://github.com/GeyserMC/Geyser/issues/3388
pong . motd ( pong . motd ( ) . replace ( ';' , ':' ) ) ;
pong . subMotd ( pong . subMotd ( ) . replace ( ';' , ':' ) ) ;
2022-10-30 22:00:08 +00:00
// Fallbacks to prevent errors and allow Bedrock to see the server
if ( pong . motd ( ) = = null | | pong . motd ( ) . isBlank ( ) ) {
pong . motd ( GeyserImpl . NAME ) ;
}
if ( pong . subMotd ( ) = = null | | pong . subMotd ( ) . isBlank ( ) ) {
// Sub-MOTD cannot be empty as of 1.16.210.59
pong . subMotd ( GeyserImpl . NAME ) ;
}
2023-09-06 03:16:44 +00:00
if ( ConnectionTestCommand . CONNECTION_TEST_MOTD ! = null ) {
// Force-override as we are testing the connection and want to verify we are connecting to the right server through the MOTD
pong . motd ( ConnectionTestCommand . CONNECTION_TEST_MOTD ) ;
pong . subMotd ( GeyserImpl . NAME ) ;
}
2022-10-30 22:00:08 +00:00
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte [ ] motdArray = pong . motd ( ) . getBytes ( StandardCharsets . UTF_8 ) ;
int subMotdLength = pong . subMotd ( ) . getBytes ( StandardCharsets . UTF_8 ) . length ;
if ( motdArray . length + subMotdLength > ( MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH ) ) {
// Shorten the sub-MOTD first since that only appears locally
if ( subMotdLength > BRAND_BYTES_LENGTH ) {
pong . subMotd ( GeyserImpl . NAME ) ;
subMotdLength = BRAND_BYTES_LENGTH ;
}
if ( motdArray . length > ( MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength ) ) {
// If the top MOTD is still too long, we chop it down
byte [ ] newMotdArray = new byte [ MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength ] ;
System . arraycopy ( motdArray , 0 , newMotdArray , 0 , newMotdArray . length ) ;
pong . motd ( new String ( newMotdArray , StandardCharsets . UTF_8 ) ) ;
}
}
//Bedrock will not even attempt a connection if the client thinks the server is full
//so we have to fake it not being full
if ( pong . playerCount ( ) > = pong . maximumPlayerCount ( ) ) {
pong . maximumPlayerCount ( pong . playerCount ( ) + 1 ) ;
}
return pong ;
}
2023-11-13 23:17:40 +00:00
/ * *
* @return the throwable from the given supplier , or the throwable caught while calling the supplier .
* /
private static Throwable throwableOrCaught ( Supplier < Throwable > supplier ) {
try {
return supplier . get ( ) ;
} catch ( Throwable throwable ) {
return throwable ;
}
}
2022-10-30 22:00:08 +00:00
private static Transport compatibleTransport ( ) {
TransportHelper . TransportMethod transportMethod = TransportHelper . determineTransportMethod ( ) ;
if ( transportMethod = = TransportHelper . TransportMethod . EPOLL ) {
return new Transport ( EpollDatagramChannel . class , EpollEventLoopGroup : : new ) ;
}
if ( transportMethod = = TransportHelper . TransportMethod . KQUEUE ) {
return new Transport ( KQueueDatagramChannel . class , KQueueEventLoopGroup : : new ) ;
}
// if (transportMethod == TransportHelper.TransportMethod.IO_URING) {
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
// }
return new Transport ( NioDatagramChannel . class , NioEventLoopGroup : : new ) ;
}
2023-04-07 01:47:37 +00:00
private record Transport ( Class < ? extends DatagramChannel > datagramChannel , IntFunction < EventLoopGroup > eventLoopGroupFactory ) {
2022-10-30 03:02:11 +00:00
}
2022-10-30 00:23:21 +00:00
}