Ajout du NetworkAPI

This commit is contained in:
2015-02-04 17:42:58 -05:00
parent 6819ff2ee7
commit 1a8a911a12
18 changed files with 753 additions and 11 deletions

79
NetworkAPI protocole.md Normal file
View File

@@ -0,0 +1,79 @@
Protocole de NetworkAPI
====================
Ce fichier d<>fini simplement le fonctionnement du protocole qui sera impl<70>ment<6E> dans le package `net.mc_pandacraft.java.plugin.pandacraftutils.network_api`
Il servira <20> faire communiquer le serveur Minecraft avec l'interface d'administration ou le site internet
## Type de connexion
Connexion TCP
## Cryptage
Les communications devant rester dans un r<>seau priv<69> (loopback, VNP) entre les syst<73>mes composant le serveur, il n'est pas vraiment n<>cessaire de crypter les connexions. En d'autres termes, les clients effectuant les requ<71>tes ne correspondent pas aux utilisateurs finaux, se trouvant sur les r<>seaux d'acc<63>s.
## S<>curit<69>
M<EFBFBD>me si le cryptage ne semble pas n<>cessaire, il faut tout de m<>me s<>curiser avec un syst<73>me de "mot de passe", qui sera d<>crit en dessous
## Structure d'une requ<71>te
La requ<71>te est construite en mode texte, sur plusieurs lignes. Chaque ligne se fini par un '\n' :
- Mot de passe alphanum<75>rique + '\n'
- Commande principale + '\n'
- Longueur des donn<6E>es, en octets + '\n'
- Valeur/donn<6E>es (pas de '\n' <20> la fin)
Pour que l'analyse de la requ<71>te c<>t<EFBFBD> serveur puisse s'effectuer c<>t<EFBFBD> serveur, le flux qui va du client vers le serveur doit <20>tre ferm<72>e apr<70>s envoi des donn<6E>es.
### Exemple de paquet
ervg1e3r2c
command
12
say Salut :)
### Les commandes principaux
#### `command`
Ex<EFBFBD>cute la commande pass<73>e dans la partie **donn<6E>e** de la requ<71>te, comme si elle avait <20>t<EFBFBD> ex<65>cut<75>e par la console (`ConsoleCommandSender` dans Bukkit). Le r<>sultat de l'ex<65>cution de la commande (valeur de retour de CommandExecutor.onCommand()) ne peux pas <20>tre retourn<72> en r<>ponse. Cependant, la non ex<65>cution de la commande sera indiqu<71> dans la console du serveur
#### `command_async`
Pareil que `command` mais cette fois, celle-ci n'est pas forc<72>ment ex<65>cut<75>e dans le thread principal. Attention : certaines commandes ne peuvent pas fonctionner en asynchrone. Cette m<>thode est utile dans le cas o<> le thread principal ne r<>pond plus
#### `broadcast`
Affiche un message sur le chat pour tout le monde connect<63>.
Le message pass<73> dans la partie **donn<6E>e** est diffus<75> tel quel dans le chat et sur la console (attention <20> la bonne utilisation des codes couleurs)
#### `player_list`
Renvoi la liste des joueurs connect<63>s. La valeur pass<73> dans la partie **donn<6E>e** doit contenir, selon les besoins :
- `disp_name` pour donner les noms d'affichage du genre [Admin]Pseudo
- `location` pour donner la localisation du joueur
- `ip` pour donner l'IP du joueur
- ...
## Structure d'une r<>ponse
La r<>ponse est construite en mode texte, sur plusieurs lignes. Chaque ligne se fini par un '\n' :
- Status de r<>ponse (`OK` ou `ERROR`) + '\n'
- Longueur des donn<6E>es
- Donn<6E>es, si n<>cessaire,ou message d'erreur, si status != `ok`
### Exemple de paquets
R<EFBFBD>ponse <20> une requ<71>te sans r<>sultat qui a bien <20>t<EFBFBD> ex<65>cut<75>
OK
0
----
R<EFBFBD>ponse <20> une commande inexistante
ERROR
24
command_not_exists
----
R<EFBFBD>ponse <20> une requ<71>te de type `player_list`
OK
56
player\0disp_name
marcbal\0<>f[<5B>4Admin<69>f]<5D>4marcbal<61>r

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jardesc>
<jar path="PandacraftUtils/jar_export/PandacraftUtils-3.4.jar"/>
<jar path="PandacraftUtils/jar_export/PandacraftUtils-3.5.jar"/>
<options buildIfNeeded="true" compress="true" descriptionLocation="/PandacraftUtils/make_jar.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
<storedRefactorings deprecationInfo="true" structuralOnly="false"/>
<selectedProjects/>

View File

@@ -1,6 +1,6 @@
name: PandacraftUtils
main: net.mc_pandacraft.java.plugin.pandacraftutils.PandacraftUtils
version: 3.4
version: 3.5
@@ -67,7 +67,7 @@ commands:
aliases: [am]
admin:
description: Administration
usage: /admin <reload|version>
usage: /admin [reload [config|network]]
permission: pandacraft.admin

View File

@@ -101,9 +101,14 @@ public class ConfigManager {
// TODO ajouter les valeurs par défaut dans config.yml
EntitySpam_worlds = "creative"; // séparé avec des point-virgules
EntitySpam_limitPerChunks = 50;
// TODO ajouter les valeurs par défaut dans config.yml
NetworkAPI_passwd = "rgrgaethejtrvvzh47";
initChatAnalysisBadWord();
@@ -147,6 +152,18 @@ public class ConfigManager {
/*
* NetworkAPI
*/
public String NetworkAPI_passwd;
/*
* Automessages

View File

@@ -16,6 +16,7 @@ import net.mc_pandacraft.java.plugin.pandacraftutils.modules.WESelectionDisplayM
import net.mc_pandacraft.java.plugin.pandacraftutils.modules.cheat_protect.CreativCheatManager;
import net.mc_pandacraft.java.plugin.pandacraftutils.modules.cheat_protect.EntitySpamManager;
import net.mc_pandacraft.java.plugin.pandacraftutils.modules.cheat_protect.NoPvpProtectManager;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.NetworkAPI;
import net.mc_pandacraft.java.plugin.pandacraftutils.players.OnlinePlayerManager;
import org.bukkit.entity.Player;
@@ -87,9 +88,9 @@ public class PandacraftUtils extends JavaPlugin {
tpsAnalysisManager = new TPSAnalysisManager();
autoMessagesManager = new AutoMessagesManager();
entitySpamManager = new EntitySpamManager();
NetworkAPI.loadNewInstance();
getServer().getScheduler().runTaskLater(this, new Runnable() {
@Override public void run() { new PlayerDataCleaner(instance); }

View File

@@ -1,6 +1,8 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.commands;
import net.mc_pandacraft.java.plugin.pandacraftutils.ConfigManager;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.NetworkAPI;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.NetworkAPIListener;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
@@ -15,17 +17,31 @@ public class CommandAdmin extends AbstractCommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label,
String[] args) {
if (args.length == 0) return false;
if (args[0].equalsIgnoreCase("reload")) {
ConfigManager.reloadConfig();
sender.sendMessage(ChatColor.GREEN+"La configuration de PandacraftUtils viens d'être rechargée");
if (args.length == 0){
sender.sendMessage(ChatColor.GREEN+plugin.getDescription().getFullName());
NetworkAPIListener nwAPI = NetworkAPIListener.getInstance();
if (nwAPI.isAlive())
sender.sendMessage(ChatColor.GREEN+"NetworkAPI écoute au port "+NetworkAPIListener.getInstance().getPort());
else
sender.sendMessage(ChatColor.GREEN+"NetworkAPI n'est pas démarré. Voir la console pour les détails, ou faites /admin reload ");
return true;
}
if (args[0].equalsIgnoreCase("version")) {
sender.sendMessage(ChatColor.GREEN+plugin.getDescription().getFullName());
return true;
if (args[0].equalsIgnoreCase("reload") && args.length >= 2) {
if (args[1].equalsIgnoreCase("config")) {
ConfigManager.reloadConfig();
sender.sendMessage(ChatColor.GREEN+"La configuration de PandacraftUtils viens d'être rechargée");
return true;
}
if (args[1].equalsIgnoreCase("network")) {
sender.sendMessage(ChatColor.GREEN+"Redémarrage de NetworkAPI");
NetworkAPI.loadNewInstance();
sender.sendMessage(ChatColor.GREEN+"Redémarrage terminée");
return true;
}
}

View File

@@ -0,0 +1,33 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors.RequestExecutorBroadcast;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors.RequestExecutorCommand;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors.RequestExecutorCommandAsync;
public class NetworkAPI {
public static void loadNewInstance() {
NetworkAPIListener.loadNewInstance();
/*
* Initialisation des exécuteurs des commandes réseau
* LEs constructeurs s'occupent eux même de se référencer dans l'instance de la classe NetworkAPIListener
*/
new RequestExecutorCommand();
new RequestExecutorBroadcast();
new RequestExecutorCommandAsync();
}
/*
public static void main(String[] args) throws Throwable {
loadNewInstance();
System.out.println(NetworkAPIListener.getInstance().getCommandList());
NetworkAPIListener.getInstance().join();
}
*/
}

View File

@@ -0,0 +1,191 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.HashMap;
import org.bukkit.ChatColor;
import net.mc_pandacraft.java.plugin.pandacraftutils.PandacraftUtils;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.RequestAnalyser.BadRequestException;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors.AbstractRequestExecutor;
public class NetworkAPIListener extends Thread {
private static NetworkAPIListener instance;
/**
* Retourne l'unique instance de la classe. Si elle n'existe pas, on tente de la créer.<br/>
* Cette classe étant un enfant de Thread, la création de l'instance provoque son exécution
* @return L'unique instance de la classe
*/
public synchronized static NetworkAPIListener getInstance() {
if (instance == null)
loadNewInstance();
return instance;
}
public synchronized static void loadNewInstance() {
if (instance != null && instance.isAlive()) {
instance.closeServerSocket(); // cet appel est censé provoquer l'arrêt du thread
try { instance.join(); } catch (InterruptedException e) { }
}
instance = new NetworkAPIListener();
instance.start();
}
private PandacraftUtils plugin = PandacraftUtils.getInstance();
private int port = plugin.getServer().getPort()+9;
private ServerSocket serverSocket;
private HashMap<String, AbstractRequestExecutor> requestExecutors = new HashMap<String, AbstractRequestExecutor>();
private NetworkAPIListener() {
super("NetworkAPI Listener");
System.setProperty("file.encoding", "UTF-8");
plugin.getServer().getLogger().info("System property 'file.encoding' was set to 'UTF-8'");
}
@Override
public void run() {
synchronized (this) {
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
plugin.getServer().getLogger().severe(e.getMessage());
System.err.println(e.getMessage());
return;
}
}
plugin.getServer().getLogger().info(ChatColor.GREEN+"NetworkAPIListener à l'écoute sur le port "+port);
int i=0;
try {
// réception des connexion client
while (!serverSocket.isClosed()) {
Socket socketClient = serverSocket.accept();
new PacketExecutor(socketClient, i++).start();
}
} catch(IOException e) { }
synchronized (this) {
try {
if (!serverSocket.isClosed())
serverSocket.close();
} catch (IOException e) { }
}
plugin.getServer().getLogger().info(ChatColor.GREEN+"NetworkAPIListener ferme le port "+port);
}
/**
* Ferme le ServerSocket. Ceci provoque l'arrêt du thread associé à l'instance de la classe
*/
private synchronized void closeServerSocket() {
if (serverSocket != null)
{
try {
serverSocket.close();
} catch (IOException e) { }
}
}
public int getPort() { return port; }
public void registerRequestExecutor(String command, AbstractRequestExecutor executor) {
requestExecutors.put(command, executor);
}
public AbstractRequestExecutor getRequestExecutor(String command) {
return requestExecutors.get(command);
}
public String getCommandList() {
return Arrays.toString(requestExecutors.keySet().toArray());
}
}
/**
* Prends en charge un socket client et le transmet au gestionnaire de paquet correspondant.<br/>
* La connexion est fermée après chaque requête du client (règle pouvant évoluer)
* @author Marc
*
*/
class PacketExecutor extends Thread {
private PandacraftUtils plugin = PandacraftUtils.getInstance();
private Socket socket;
public PacketExecutor(Socket s, int iteration) {
super("NetworkAPI Input Packet #"+iteration);
socket = s;
}
@Override
public void run() {
try {
// analyse de la requête
RequestAnalyser analyse = new RequestAnalyser(socket);
AbstractRequestExecutor executor = NetworkAPIListener.getInstance().getRequestExecutor(analyse.command);
executor.execute(analyse.data, socket);
} catch (IOException e) {
plugin.getServer().getLogger().severe("IOException "+e.getMessage());
} catch (BadRequestException e) {
Response rep = new Response();
rep.good = false;
rep.data = e.getMessage();
try {
rep.sendPacket(new PrintStream(socket.getOutputStream()));
} catch (IOException e1) { }
plugin.getServer().getLogger().severe("BadRequestException "+e.getMessage());
} catch(Throwable e) {
e.printStackTrace();
}
try {
socket.close();
} catch (Exception e) { }
}
}

View File

@@ -0,0 +1,87 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import net.mc_pandacraft.java.plugin.pandacraftutils.ConfigManager;
public class RequestAnalyser {
private NetworkAPIListener networkAPIListener = NetworkAPIListener.getInstance();
private final String pass = ConfigManager.getInstance().NetworkAPI_passwd;
public final String command;
public final String data;
public RequestAnalyser(Socket socket) throws IOException, BadRequestException {
if (socket == null || socket.isClosed() || socket.isInputShutdown()) throw new IllegalArgumentException("le socket doit être non null et doit être ouvert sur le flux d'entrée");
// on lis la réponse
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
// lecture de la première ligne
line = in.readLine();
if (line == null || !line.equals(pass))
throw new BadRequestException("wrong_password");
// lecture de la deuxième ligne
line = in.readLine();
if (line == null || networkAPIListener.getRequestExecutor(line) == null)
throw new BadRequestException("command_not_exists");
command = line;
// lecture de la troisième ligne
line = in.readLine();
int data_size = 0;
try {
data_size = Integer.parseInt(line);
} catch (NumberFormatException e) { throw new BadRequestException("wrong_data_size_format"); }
// lecture du reste
StringBuilder sB_data = new StringBuilder();
char[] c = new char[100];
int nbC = 0;
while((nbC = in.read(c)) != -1)
sB_data.append(c, 0, nbC);
data = sB_data.toString();
if (data.length() != data_size)
throw new BadRequestException("wrong_data_size");
socket.shutdownInput();
}
public class BadRequestException extends Exception {
private static final long serialVersionUID = 1L;
public BadRequestException(String message) {
super(message);
}
}
}

View File

@@ -0,0 +1,19 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api;
import java.io.PrintStream;
public class Response {
public boolean good = true;
public String data = "";
public void sendPacket(PrintStream out) {
if (data == null) data = "";
out.print((good?"OK":"ERROR")+"\n");
out.print(data.length()+"\n");
out.print(data);
out.flush();
}
}

View File

@@ -0,0 +1,31 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.client_test;
import java.io.PrintStream;
public abstract class AbstractRequest {
private final String pass = "test";
private String command;
private String data;
protected AbstractRequest(String cmd) {
if (cmd == null || cmd.isEmpty()) throw new IllegalArgumentException("Un message doit-être défini");
command = cmd;
}
protected void setData(String d) {
if (d == null) d = "";
data = d;
}
public void sendPacket(PrintStream out) {
out.print(pass+"\n");
out.print(command+"\n");
out.print(data.length()+"\n");
out.print(data);
out.flush();
}
}

View File

@@ -0,0 +1,37 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.client_test;
import java.io.PrintStream;
import java.net.Socket;
public class ClientTest {
public static void main(String[] args) throws Exception {
System.out.println("--- connexion ...");
Socket s = new Socket("localhost", 12485);
System.out.println("--- connecté");
PrintStream out = new PrintStream(s.getOutputStream());
AbstractRequest request = new RequestCommand("say Salut :)");
request.sendPacket(out);
s.shutdownOutput();
System.out.println("--- requête envoyée");
request.sendPacket(System.out);
System.out.println();
System.out.println("--- réponse du serveur :");
ResponseAnalyser response = new ResponseAnalyser(s);
System.out.println(response.good);
System.out.println(response.data);
s.close();
}
}

View File

@@ -0,0 +1,12 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.client_test;
public class RequestCommand extends AbstractRequest {
public RequestCommand(String command) {
super("command");
setData(command);
}
}

View File

@@ -0,0 +1,63 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.client_test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ResponseAnalyser {
/**
* Indique si la requête s'est bien exécutée (l'entête de la réponse est 'ok')
*/
public final boolean good;
public final String data;
public ResponseAnalyser(Socket socket) throws IOException {
if (socket == null || socket.isClosed() || socket.isInputShutdown()) throw new IllegalArgumentException("le socket doit être non null et doit être ouvert sur le flux d'entrée");
// on lis la réponse
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
// lecture de la première ligne
line = in.readLine();
good = line.equalsIgnoreCase("OK");
// lecture de la deuxième ligne
line = in.readLine();
int data_size = 0;
try {
data_size = Integer.parseInt(line);
} catch (NumberFormatException e) { throw new RuntimeException("Réponse mal formée : la deuxième ligne doit-être un nombre entier"); }
// lecture du reste
StringBuilder sB_data = new StringBuilder();
char[] c = new char[100];
int nbC = 0;
while((nbC = in.read(c)) != -1)
sB_data.append(c, 0, nbC);
data = sB_data.toString();
if (data.length() != data_size)
throw new RuntimeException("Réponse mal formée : "+data_size+" caractères annoncée dans la requête, mais "+data.length()+" s'y trouvent.");
}
}

View File

@@ -0,0 +1,48 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import net.mc_pandacraft.java.plugin.pandacraftutils.PandacraftUtils;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.NetworkAPIListener;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.Response;
public abstract class AbstractRequestExecutor {
protected PandacraftUtils plugin = PandacraftUtils.getInstance();
public final String command;
public AbstractRequestExecutor(String cmd) {
command = cmd.toLowerCase();
NetworkAPIListener.getInstance().registerRequestExecutor(command, this);
}
public void execute(String data, Socket socket) throws IOException {
if (socket == null || socket.isClosed() || socket.isOutputShutdown()) throw new IllegalArgumentException("le socket doit être non null et doit être ouvert sur le flux d'entrée");
Response rep = run(data);
rep.sendPacket(new PrintStream(socket.getOutputStream()));
}
/**
*
* @param data La représentation sous forme de String des données envoyés dans la requête
* @return La réponse à retourner au client
*/
protected abstract Response run(String data);
}

View File

@@ -0,0 +1,23 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.Response;
public class RequestExecutorBroadcast extends AbstractRequestExecutor {
public RequestExecutorBroadcast() {
super("broadcast");
}
@Override
protected Response run(String data) {
plugin.broadcast(data, false);
Response rep = new Response();
rep.good = true;
rep.data = "";
return rep;
}
}

View File

@@ -0,0 +1,49 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.Response;
public class RequestExecutorCommand extends AbstractRequestExecutor {
public RequestExecutorCommand() {
super("command");
// TODO Auto-generated constructor stub
}
@Override
protected Response run(String data) {
plugin.getServer().getScheduler().runTask(plugin, new Runnable() {
String command;
public void run() {
try
{
boolean succes = plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), command);
if (!succes)
plugin.getLogger().warning("Can't execute command");
}
catch (Exception e)
{
plugin.getLogger().severe(e.getMessage());
}
}
public Runnable init(String cmd) {
command = cmd;
return this;
}
}.init(data));
Response rep = new Response();
rep.good = true;
rep.data = "";
return rep;
}
}

View File

@@ -0,0 +1,36 @@
package net.mc_pandacraft.java.plugin.pandacraftutils.network_api.request_executors;
import net.mc_pandacraft.java.plugin.pandacraftutils.network_api.Response;
public class RequestExecutorCommandAsync extends AbstractRequestExecutor {
public RequestExecutorCommandAsync() {
super("command_async");
}
@Override
protected Response run(String data) {
Response rep = new Response();
try
{
rep.good = plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), data);
if (!rep.good)
{
plugin.getLogger().warning("Can't execute command");
rep.data = "command sent but CommandExecutor has not return success";
}
}
catch (Exception e)
{
plugin.getLogger().severe(e.getMessage());
rep.good = false;
rep.data = "Exception : "+e.getMessage();
}
return rep;
}
}