Implement basic udp query to close #185
This commit is contained in:
parent
14389eb370
commit
32a5271dc3
@ -61,4 +61,12 @@ public class ListenerInfo
|
||||
* server (force default server).
|
||||
*/
|
||||
private final boolean pingPassthrough;
|
||||
/**
|
||||
* What port to run udp query on.
|
||||
*/
|
||||
private final int queryPort;
|
||||
/**
|
||||
* Whether to enable udp query.
|
||||
*/
|
||||
private final boolean queryEnabled;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
<name>BungeeCord-Config</name>
|
||||
<description>Generic java configuration API intended for use with BungeeCord</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
|
1
pom.xml
1
pom.xml
@ -42,6 +42,7 @@
|
||||
<module>event</module>
|
||||
<module>protocol</module>
|
||||
<module>proxy</module>
|
||||
<module>query</module>
|
||||
</modules>
|
||||
|
||||
<scm>
|
||||
|
@ -53,6 +53,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-query</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.trove4j</groupId>
|
||||
<artifactId>trove4j</artifactId>
|
||||
|
@ -60,6 +60,7 @@ import net.md_5.bungee.protocol.packet.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.Packet3Chat;
|
||||
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
|
||||
import net.md_5.bungee.protocol.Vanilla;
|
||||
import net.md_5.bungee.query.RemoteQuery;
|
||||
import net.md_5.bungee.tab.Custom;
|
||||
import net.md_5.bungee.util.CaseInsensitiveMap;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
@ -269,6 +270,26 @@ public class BungeeCord extends ProxyServer
|
||||
.group( eventLoops )
|
||||
.localAddress( info.getHost() )
|
||||
.bind().addListener( listener );
|
||||
|
||||
if ( info.isQueryEnabled() )
|
||||
{
|
||||
ChannelFutureListener bindListener = new ChannelFutureListener()
|
||||
{
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception
|
||||
{
|
||||
if ( future.isSuccess() )
|
||||
{
|
||||
listeners.add( future.channel() );
|
||||
getLogger().info( "Started query on " + future.channel().localAddress() );
|
||||
} else
|
||||
{
|
||||
getLogger().log( Level.WARNING, "Could not bind to host " + future.channel().remoteAddress(), future.cause() );
|
||||
}
|
||||
}
|
||||
};
|
||||
new RemoteQuery( this, info ).start( new InetSocketAddress( info.getHost().getAddress(), info.getQueryPort() ), eventLoops, bindListener );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,10 @@ public class YamlConfig implements ConfigurationAdapter
|
||||
boolean setLocalAddress = get( "bind_local_address", true, val );
|
||||
boolean pingPassthrough = get( "ping_passthrough", false, val );
|
||||
|
||||
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.clazz, setLocalAddress, pingPassthrough );
|
||||
boolean query = get( "guery_enabled", false, val );
|
||||
int queryPort = get( "query_port", 25577, val );
|
||||
|
||||
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.clazz, setLocalAddress, pingPassthrough, queryPort, query );
|
||||
ret.add( info );
|
||||
}
|
||||
|
||||
|
31
query/nb-configuration.xml
Normal file
31
query/nb-configuration.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project-shared-configuration>
|
||||
<!--
|
||||
This file contains additional configuration written by modules in the NetBeans IDE.
|
||||
The configuration is intended to be shared among all the users of project and
|
||||
therefore it is assumed to be part of version control checkout.
|
||||
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
|
||||
-->
|
||||
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
|
||||
<!--
|
||||
Properties that influence various parts of the IDE, especially code formatting and the like.
|
||||
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
|
||||
That way multiple projects can share the same settings (useful for formatting rules for example).
|
||||
Any value defined here will override the pom.xml file value but is only applicable to the current project.
|
||||
-->
|
||||
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
|
||||
</properties>
|
||||
</project-shared-configuration>
|
35
query/pom.xml
Normal file
35
query/pom.xml
Normal file
@ -0,0 +1,35 @@
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-query</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Query</name>
|
||||
<description>Minecraft query implementation based on the BungeeCord API.</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
145
query/src/main/java/net/md_5/bungee/query/QueryHandler.java
Normal file
145
query/src/main/java/net/md_5/bungee/query/QueryHandler.java
Normal file
@ -0,0 +1,145 @@
|
||||
package net.md_5.bungee.query;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.AddressedEnvelope;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
{
|
||||
|
||||
private final ProxyServer bungee;
|
||||
private final ListenerInfo listener;
|
||||
/*========================================================================*/
|
||||
private final Random random = new Random();
|
||||
private final Map<Integer, Long> sessions = new HashMap<>();
|
||||
|
||||
private void writeShort(ByteBuf buf, int s)
|
||||
{
|
||||
buf.order( ByteOrder.LITTLE_ENDIAN ).writeShort( s );
|
||||
}
|
||||
|
||||
private void writeNumber(ByteBuf buf, int i)
|
||||
{
|
||||
writeString( buf, Integer.toString( i ) );
|
||||
}
|
||||
|
||||
private void writeString(ByteBuf buf, String s)
|
||||
{
|
||||
for ( char c : s.toCharArray() )
|
||||
{
|
||||
buf.writeChar( c );
|
||||
}
|
||||
buf.writeByte( 0x00 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception
|
||||
{
|
||||
super.channelActive( ctx ); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception
|
||||
{
|
||||
ByteBuf in = msg.content();
|
||||
if ( in.readUnsignedByte() != 0xFE && in.readUnsignedByte() != 0xFD )
|
||||
{
|
||||
throw new IllegalStateException( "Incorrect magic!" );
|
||||
}
|
||||
|
||||
ByteBuf out = ctx.alloc().buffer();
|
||||
AddressedEnvelope response = new DatagramPacket( out, msg.sender() );
|
||||
|
||||
byte type = in.readByte();
|
||||
int sessionId = in.readInt();
|
||||
|
||||
if ( type == 0x09 )
|
||||
{
|
||||
out.writeByte( 0x09 );
|
||||
out.writeInt( sessionId );
|
||||
|
||||
int challengeToken = random.nextInt();
|
||||
sessions.put( challengeToken, System.currentTimeMillis() );
|
||||
|
||||
writeNumber( out, challengeToken );
|
||||
}
|
||||
|
||||
if ( type == 0x00 )
|
||||
{
|
||||
int challengeToken = out.readInt();
|
||||
Long session = sessions.get( challengeToken );
|
||||
if ( session == null || System.currentTimeMillis() - session > TimeUnit.SECONDS.toMillis( 30 ) )
|
||||
{
|
||||
throw new IllegalStateException( "No session!" );
|
||||
}
|
||||
|
||||
out.writeByte( 0x00 );
|
||||
out.writeInt( sessionId );
|
||||
|
||||
if ( in.readableBytes() == 0 )
|
||||
{
|
||||
// Short response
|
||||
writeString( out, listener.getMotd() ); // MOTD
|
||||
writeString( out, "SMP" ); // Game Type
|
||||
writeString( out, "BungeeCord_Proxy" ); // World Name
|
||||
writeNumber( out, bungee.getOnlineCount() ); // Online Count
|
||||
writeNumber( out, listener.getMaxPlayers() ); // Max Players
|
||||
writeShort( out, listener.getHost().getPort() ); // Port
|
||||
writeString( out, listener.getHost().getHostString() ); // IP
|
||||
} else if ( in.readableBytes() == 8 )
|
||||
{
|
||||
// Long Response
|
||||
out.writeBytes( new byte[ 11 ] );
|
||||
Map<String, String> data = new HashMap<>();
|
||||
|
||||
data.put( "hostname", listener.getMotd() );
|
||||
data.put( "gametype", "SMP" );
|
||||
// Start Extra Info
|
||||
data.put( "game_id", "MINECRAFT" );
|
||||
data.put( "version", bungee.getGameVersion() );
|
||||
// data.put( "plugins","");
|
||||
// End Extra Info
|
||||
data.put( "map", "BungeeCord_Proxy" );
|
||||
data.put( "numplayers", Integer.toString( bungee.getOnlineCount() ) );
|
||||
data.put( "maxplayers", Integer.toString( listener.getMaxPlayers() ) );
|
||||
data.put( "hostport", Integer.toString( listener.getHost().getPort() ) );
|
||||
data.put( "hostip", listener.getHost().getHostString() );
|
||||
|
||||
for ( Map.Entry<String, String> entry : data.entrySet() )
|
||||
{
|
||||
writeString( out, entry.getKey() );
|
||||
writeString( out, entry.getValue() );
|
||||
|
||||
}
|
||||
out.writeByte( 0x00 ); // Null
|
||||
|
||||
// Padding
|
||||
out.writeBytes( new byte[ 10 ] );
|
||||
// Player List
|
||||
for ( ProxiedPlayer p : bungee.getPlayers() )
|
||||
{
|
||||
writeString( out, p.getName() );
|
||||
}
|
||||
out.writeByte( 0x00 ); // Null
|
||||
} else
|
||||
{
|
||||
// Error!
|
||||
throw new IllegalStateException( "Invalid data request packet" );
|
||||
}
|
||||
}
|
||||
|
||||
ctx.writeAndFlush( response );
|
||||
}
|
||||
}
|
29
query/src/main/java/net/md_5/bungee/query/RemoteQuery.java
Normal file
29
query/src/main/java/net/md_5/bungee/query/RemoteQuery.java
Normal file
@ -0,0 +1,29 @@
|
||||
package net.md_5.bungee.query;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import java.net.InetSocketAddress;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class RemoteQuery
|
||||
{
|
||||
|
||||
private final ProxyServer bungee;
|
||||
private final ListenerInfo listener;
|
||||
|
||||
public void start(InetSocketAddress address, EventLoopGroup eventLoop, ChannelFutureListener future)
|
||||
{
|
||||
new Bootstrap()
|
||||
.channel( NioDatagramChannel.class )
|
||||
.group( eventLoop )
|
||||
.handler( new QueryHandler( bungee, listener ) )
|
||||
.localAddress( address )
|
||||
.bind().addListener( future );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user