Add packet logging for debugging

This commit is contained in:
Dan Mulloy 2017-03-01 17:02:46 -05:00
parent 57cfbc4f81
commit 9289825d87
5 changed files with 259 additions and 6 deletions

View File

@ -715,11 +715,11 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
}
}
private static String format(Protocol protocol, Sender sender, String name) {
public static String format(Protocol protocol, Sender sender, String name) {
if (name.contains("Packet"))
return name;
return String.format("Packet%s%s%s", protocol.getPacketName(), sender.getPacketName(), name);
return String.format("Packet%s%s%s", protocol.getPacketName(), sender.getPacketName(), WordUtils.capitalize(name));
}
/**

View File

@ -149,9 +149,19 @@ public class WirePacket {
* @return The resulting WirePacket
*/
public static WirePacket fromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
int id = packet.getType().getCurrentId();
return new WirePacket(id, getBytes(bufferFromPacket(packet)));
}
/**
* Creates a ByteBuf from an existing PacketContainer containing all the
* bytes from that packet
*
* @param packet Existing packet
* @return The ByteBuf
*/
public static ByteBuf bufferFromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
ByteBuf buffer = PacketContainer.createPacketBuffer();
Method write = MinecraftMethods.getPacketWriteByteBufMethod();
@ -162,7 +172,7 @@ public class WirePacket {
throw new RuntimeException("Failed to serialize packet contents.", ex);
}
return new WirePacket(id, getBytes(buffer));
return buffer;
}
/**

View File

@ -0,0 +1,232 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 Dan Mulloy
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
/**
* Logs packets to a given stream
* @author dmulloy2
*/
public class PacketLogging implements CommandExecutor, PacketListener {
public static final String NAME = "packetlog";
private List<PacketType> sendingTypes = new ArrayList<>();
private List<PacketType> receivingTypes = new ArrayList<>();
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Logger fileLogger;
private LogLocation location = LogLocation.FILE;
private final ProtocolManager manager;
private final Plugin plugin;
PacketLogging(Plugin plugin, ProtocolManager manager) {
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
PacketType type = null;
if (args.length > 2) {
Protocol protocol;
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
Sender pSender;
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
try {
try {
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) {
type = PacketType.findCurrent(protocol, pSender, args[2]);
}
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + PacketType.format(protocol, pSender, args[2]));
return true;
}
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
return true;
}
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
return true;
}
private void startLogging() {
manager.removePacketListener(this);
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
return;
}
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
if (location == LogLocation.FILE && fileLogger == null) {
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
for (Handler handler : fileLogger.getHandlers())
fileLogger.removeHandler(handler);
fileLogger.setUseParentHandlers(false);
try {
File logFile = new File(plugin.getDataFolder(), "log.log");
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
handler.setFormatter(new LogFormatter());
fileLogger.addHandler(handler);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
return;
}
}
manager.addPacketListener(this);
}
@Override
public void onPacketSending(PacketEvent event) {
log(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
log(event);
}
private void log(PacketEvent event) {
ByteBuf buffer = WirePacket.bufferFromPacket(event.getPacket());
String hexDump = ByteBufUtil.hexDump(buffer);
if (location == LogLocation.FILE) {
fileLogger.log(Level.INFO, event.getPacketType() + ":");
fileLogger.log(Level.INFO, hexDump);
fileLogger.log(Level.INFO, "");
} else {
System.out.println(event.getPacketType() + ":");
System.out.println(hexDump);
System.out.println();
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
private static enum LogLocation {
CONSOLE,
FILE;
}
private static class LogFormatter extends Formatter {
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String FORMAT = "[{0}] {1}";
@Override
public String format(LogRecord record) {
String string = formatMessage(record);
if (string.isEmpty()) {
return LINE_SEPARATOR;
}
StringBuilder message = new StringBuilder();
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
message.append(LINE_SEPARATOR);
return message.toString();
}
}
}

View File

@ -90,7 +90,8 @@ public class ProtocolLib extends JavaPlugin {
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL
PROTOCOL,
LOGGING;
}
/**
@ -143,6 +144,7 @@ public class ProtocolLib extends JavaPlugin {
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
private CommandFilter commandFilter;
private PacketLogging packetLogging;
// Whether or not disable is not needed
private boolean skipDisable;
@ -269,6 +271,9 @@ public class ProtocolLib extends JavaPlugin {
case PACKET:
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
break;
case LOGGING:
packetLogging = new PacketLogging(this, protocolManager);
break;
}
} catch (OutOfMemoryError e) {
throw e;
@ -404,6 +409,7 @@ public class ProtocolLib extends JavaPlugin {
registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
registerCommand(PacketLogging.NAME, packetLogging);
// Player login and logout events
protocolManager.registerEvents(manager, this);

View File

@ -24,6 +24,11 @@ commands:
aliases: [packet_filter]
permission: protocol.admin
permission-message: You don't have <permission>
packetlog:
description: Logs hex representations of packets to a file or console
usage: /<command> <protocol> <sender> <packet> [location]
permission: protocol.admin
permission-message: You don't have <permission>
permissions:
protocol.*: