[BLEEDING] Net checks: Cancel redundant flying packets. Better config.

FlyingFrequency: if frequency is above 20/sec, redundant packets will be
cancelled (experimental, configurable).
This commit is contained in:
asofold 2015-01-08 03:35:25 +01:00
parent 03eb652dd9
commit dd87ab7ccd
9 changed files with 291 additions and 64 deletions

View File

@ -1,5 +1,6 @@
package fr.neatmonster.nocheatplus.net.protocollib;
import java.util.List;
import java.util.Map;
import org.bukkit.entity.Player;
@ -7,13 +8,13 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.components.JoinLeaveListener;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.net.NetConfig;
import fr.neatmonster.nocheatplus.net.NetConfigCache;
import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW;
@ -27,30 +28,44 @@ import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW;
*/
public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener {
// TODO: Silent cancel count.
// TODO: Configuration.
// TODO: Optimized options (receive only, other?).
// TODO: Forced async version ?
// TODO: Most efficient registration + optimize (primary thread or asynchronous).
private final Map<String, ActionFrequency> freqMap = new LinkedHashMapCOW<String, ActionFrequency>();
private class FFData {
public static final int numBooleans = 3;
public static final int indexOnGround = 0;
public static final int indexhasPos = 1;
public static final int indexhasLook = 2;
public final ActionFrequency all;
// Last move.
public final double[] doubles = new double[3]; // x, y, z
public final float[] floats = new float[2]; // yaw, pitch
//public final boolean[] booleans = new boolean[3]; // ground, hasPos, hasLook
public boolean onGround = false;
public FFData(int seconds) {
all = new ActionFrequency(seconds, 1000L);
}
}
private final Map<String, FFData> freqMap = new LinkedHashMapCOW<String, FFData>();
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final int idSilent = counters.registerKey("packet.flying.silentcancel");
private final int idRedundant = counters.registerKey("packet.flying.silentcancel.redundant");
private final int idNullPlayer = counters.registerKey("packet.flying.nullplayer");
private final int seconds;
private final float maxPackets;
private boolean cancelRedundant = true;
public FlyingFrequency(Plugin plugin) {
private final NetConfigCache configs;
public FlyingFrequency(NetConfigCache configs, Plugin plugin) {
// PacketPlayInFlying[3, legacy: 10]
super(plugin, PacketType.Play.Client.FLYING); // TODO: How does POS and POS_LOOK relate/translate?
ConfigFile config = ConfigManager.getConfigFile();
seconds = Math.max(1, config.getInt(ConfPaths.NET_FLYINGFREQUENCY_SECONDS));
maxPackets = Math.max(1, config.getInt(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS));
this.configs = configs;
}
@Override
public void playerJoins(Player player) {
getFreq(player.getName());
// Ignore.
}
@Override
@ -58,12 +73,12 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
freqMap.remove(player.getName());
}
private ActionFrequency getFreq(final String name) {
final ActionFrequency freq = this.freqMap.get(name);
private FFData getFreq(final String name, final NetConfig cc) {
final FFData freq = this.freqMap.get(name);
if (freq != null) {
return freq;
} else {
final ActionFrequency newFreq = new ActionFrequency(seconds, 1000);
final FFData newFreq = new FFData(cc.flyingFrequencySeconds);
this.freqMap.put(name, newFreq);
return newFreq;
}
@ -71,40 +86,84 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
@Override
public void onPacketReceiving(final PacketEvent event) {
final Player player = event.getPlayer();
if (player == null) {
if (onNullPlayer(event)) {
event.setCancelled(true);
}
// TODO: Need config?
counters.add(idNullPlayer, 1);
event.setCancelled(true);
return;
}
// TODO: Consider using similar heuristic as CB does for when to count.
// TODO: Consider detecting "untracked moves" early.
// TODO: Add more counters/cases (at least has look + has pos individually, maybe none/onground)
final ActionFrequency freq = getFreq(player.getName());
freq.add(System.currentTimeMillis(), 1f);
if (freq.score(1f) > maxPackets) {
if (onViolation(player)) {
final NetConfig cc = configs.getConfig(player.getWorld());
if (!cc.flyingFrequencyActive) {
return;
}
final FFData freq = getFreq(player.getName(), cc);
final long t = System.currentTimeMillis();
// Counting all packets.
freq.all.add(t, 1f);
final float allScore = freq.all.score(1f);
if (allScore > cc.flyingFrequencyMaxPackets) {
counters.add(idSilent, 1); // Until it is sure if we can get these async.
event.setCancelled(true);
return;
}
// Cancel redundant packets, when frequency is high anyway.
if (!cancelRedundant || !cc.flyingFrequencyCancelRedundant) {
return;
}
// TODO: Consider to detect if a moving event would fire (...).
boolean redundant = true;
final PacketContainer packet = event.getPacket();
final List<Boolean> booleans = packet.getBooleans().getValues();
if (booleans.size() != FFData.numBooleans) {
cancelRedundant = false;
return;
}
final boolean onGround = booleans.get(FFData.indexOnGround).booleanValue();
if (onGround != freq.onGround) {
redundant = false;
}
freq.onGround = onGround;
// TODO: Consider to verify on ground somehow.
if (booleans.get(FFData.indexhasPos)) {
final List<Double> doubles = packet.getDoubles().getValues();
if (doubles.size() != freq.doubles.length) {
cancelRedundant = false;
return;
}
for (int i = 0; i < freq.doubles.length; i++) {
final double val = doubles.get(i).doubleValue();
if (val != freq.doubles[i]) {
redundant = false;
freq.doubles[i] = val;
}
}
}
if (booleans.get(FFData.indexhasLook)) {
final List<Float> floats = packet.getFloat().getValues();
if (floats.size() != freq.floats.length) {
cancelRedundant = false;
return;
}
for (int i = 0; i < freq.floats.length; i++) {
final float val = floats.get(i).floatValue();
if (val != freq.floats[i]) {
redundant = false;
freq.floats[i] = val;
}
}
}
if (redundant) {
// TODO: Could check first bucket or even just 50 ms to last packet.
if (allScore / cc.flyingFrequencySeconds > 20f) {
counters.add(idRedundant, 1);
event.setCancelled(true);
}
}
}
private boolean onViolation(final Player player) {
// TODO: Get from a NetConfig (optimized).
if (ConfigManager.getConfigFile(player.getWorld().getName()).getBoolean(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE)) {
counters.add(idSilent, 1); // Until it is sure if we can get these async.
return true;
}
else {
return false;
}
}
private boolean onNullPlayer(final PacketEvent event) {
// TODO: Config (global?) ?
counters.add(idNullPlayer, 1);
return true;
}
}

View File

@ -18,6 +18,7 @@ import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.net.NetConfigCache;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
/**
@ -25,8 +26,9 @@ import fr.neatmonster.nocheatplus.utilities.StringUtil;
* @author dev1mc
*
*/
public class ProtocolLibComponent implements DisableListener, INotifyReload{
public class ProtocolLibComponent implements DisableListener, INotifyReload {
private final NetConfigCache configs = new NetConfigCache();
private final List<PacketAdapter> registeredPacketAdapters = new LinkedList<PacketAdapter>();
public ProtocolLibComponent(Plugin plugin) {
@ -34,7 +36,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{
register(plugin);
}
private void register(Plugin plugin) {
private void register(Plugin plugin) {
// Register Classes having a constructor with Plugin as argument.
if (ConfigManager.isTrueForAnyConfig(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE)) {
register(FlyingFrequency.class, plugin);
@ -55,7 +57,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{
private void register(Class<? extends PacketAdapter> clazz, Plugin plugin) {
try {
// Construct a new instance using reflection.
PacketAdapter adapter = clazz.getDeclaredConstructor(Plugin.class).newInstance(plugin);
PacketAdapter adapter = clazz.getDeclaredConstructor(Plugin.class).newInstance(configs, plugin);
ProtocolLibrary.getProtocolManager().addPacketListener(adapter);
registeredPacketAdapters.add(adapter);
} catch (Throwable t) {
@ -87,6 +89,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{
}
}
registeredPacketAdapters.clear();
configs.clearAllConfigs();
}
}

View File

@ -10,9 +10,8 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.net.NetConfig;
import fr.neatmonster.nocheatplus.net.NetConfigCache;
import fr.neatmonster.nocheatplus.utilities.TrigUtil;
public class SoundDistance extends PacketAdapter {
@ -36,14 +35,12 @@ public class SoundDistance extends PacketAdapter {
return false;
}
/** Maximum distance for thunder effects (squared). */
private final double distSq;
private final NetConfigCache configs;
private final Location useLoc = new Location(null, 0, 0, 0);
public SoundDistance(Plugin plugin) {
public SoundDistance(NetConfigCache configs, Plugin plugin) {
super(plugin, PacketType.Play.Server.NAMED_SOUND_EFFECT);
ConfigFile config = ConfigManager.getConfigFile();
double dist = config.getDouble(ConfPaths.NET_SOUNDDISTANCE_MAXDISTANCE);
distSq = dist * dist;
this.configs = configs;
}
@Override
@ -56,16 +53,18 @@ public class SoundDistance extends PacketAdapter {
}
final Player player = event.getPlayer();
final Location loc = player.getLocation(); // TODO: Use getLocation(useLoc) [synced if async].
final NetConfig cc = configs.getConfig(player.getWorld());
if (!cc.soundDistanceActive) {
return;
}
final Location loc = player.getLocation(useLoc);
// Compare distance of player to the weather location.
final StructureModifier<Integer> ints = packetContainer.getIntegers();
if (TrigUtil.distanceSquared(ints.read(0) / 8, ints.read(2) / 8, loc.getX(), loc.getZ()) > distSq) {
// TODO: Get from a NetConfig (optimized).
if (ConfigManager.getConfigFile(player.getWorld().getName()).getBoolean(ConfPaths.NET_SOUNDDISTANCE_ACTIVE)) {
event.setCancelled(true);
}
if (TrigUtil.distanceSquared(ints.read(0) / 8, ints.read(2) / 8, loc.getX(), loc.getZ()) > cc.soundDistanceSq) {
event.setCancelled(true);
}
useLoc.setWorld(null);
}
}

View File

@ -598,7 +598,6 @@ public abstract class ConfPaths {
private static final String NET_SOUNDDISTANCE = NET + "sounddistance.";
public static final String NET_SOUNDDISTANCE_ACTIVE = NET_SOUNDDISTANCE + "active";
@GlobalConfig
public static final String NET_SOUNDDISTANCE_MAXDISTANCE = NET_SOUNDDISTANCE + "maxdistance";
private static final String NET_FLYINGFREQUENCY = NET + "flyingfrequency.";
@ -607,6 +606,7 @@ public abstract class ConfPaths {
public static final String NET_FLYINGFREQUENCY_SECONDS = NET_FLYINGFREQUENCY + "seconds";
@GlobalConfig
public static final String NET_FLYINGFREQUENCY_MAXPACKETS = NET_FLYINGFREQUENCY + "maxpackets";
public static final String NET_FLYINGFREQUENCY_CANCELREDUNDANT = NET_FLYINGFREQUENCY + "cancelredundant";
public static final String STRINGS = "strings";

View File

@ -0,0 +1,79 @@
package fr.neatmonster.nocheatplus.config;
import java.util.HashMap;
import java.util.Map;
import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW;
/**
* Simple cache for configurations, adding some convenience functionality.
* @author web4web1
*
*/
public abstract class ConfigCache <K, C> {
private final Map<K, C> configs;
private final boolean cow;
/**
*
* @param cow Set to true, to use copy-on-write and synchronize writing.
* @param initialCapacity
*/
public ConfigCache(boolean cow, int initialCapacity) {
this.cow = cow;
// Linked or not linked ?
if (cow) {
configs = new LinkedHashMapCOW<K, C>(initialCapacity);
} else {
configs = new HashMap<K, C>(initialCapacity);
}
}
public boolean hasConfig(final K key) {
return configs.containsKey(key);
}
public C getConfig(final K key) {
final C config = configs.get(key);
if (config == null) {
return createConfig(key);
} else {
return config;
}
}
private C createConfig(final K key) {
final C config = newConfig(key);
if (!cow) {
configs.put(key, config);
return config;
} else {
return addConfigCOW(key, config);
}
}
private C addConfigCOW(final K key, final C config) {
// Re-check to ensure FCFS.
synchronized (configs) {
if (configs.containsKey(key)) {
return configs.get(key);
} else {
configs.put(key, config);
return config;
}
}
}
public void clearAllConfigs() {
configs.clear();
}
/**
* Factory method. In case of cow, not all returned instances might be used.
* @param key
* @return
*/
protected abstract C newConfig(final K key);
}

View File

@ -435,6 +435,7 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE, true);
set(ConfPaths.NET_FLYINGFREQUENCY_SECONDS, 5);
set(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS, 300);
set(ConfPaths.NET_FLYINGFREQUENCY_CANCELREDUNDANT, true);
// SoundDistance
set(ConfPaths.NET_SOUNDDISTANCE_ACTIVE, true);

View File

@ -0,0 +1,32 @@
package fr.neatmonster.nocheatplus.config;
import org.bukkit.World;
/**
* World-specific cache for configurations. World names are used as keys, convenience methods are added.
* @author web4web1
*
* @param <C>
*/
public abstract class WorldConfigCache<C> extends ConfigCache<String, C> {
public WorldConfigCache(boolean cow) {
this(cow, 10);
}
public WorldConfigCache(boolean cow, int initialCapacity) {
super(cow, initialCapacity);
}
public C getConfig(final World world) {
return getConfig(world.getName());
}
@Override
protected C newConfig(final String key) {
return newConfig(key, ConfigManager.getConfigFile(key));
}
protected abstract C newConfig(String key, ConfigFile configFile);
}

View File

@ -0,0 +1,37 @@
package fr.neatmonster.nocheatplus.net;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
/**
* Configuration for the net checks (fast version, sparse).
* @author web4web1
*
*/
public class NetConfig {
public final boolean flyingFrequencyActive;
public final int flyingFrequencySeconds;
public final int flyingFrequencyMaxPackets;
public final boolean flyingFrequencyCancelRedundant;
public final boolean soundDistanceActive;
/** Maximum distance for lightning effects (squared). */
public final double soundDistanceSq;
public NetConfig(final ConfigFile config) {
final ConfigFile globalConfig = ConfigManager.getConfigFile();
flyingFrequencyActive = config.getBoolean(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE);
flyingFrequencySeconds = Math.max(1, globalConfig.getInt(ConfPaths.NET_FLYINGFREQUENCY_SECONDS));
flyingFrequencyMaxPackets = Math.max(1, globalConfig.getInt(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS));
flyingFrequencyCancelRedundant = config.getBoolean(ConfPaths.NET_FLYINGFREQUENCY_CANCELREDUNDANT);
soundDistanceActive = config.getBoolean(ConfPaths.NET_SOUNDDISTANCE_ACTIVE);
double dist = config.getDouble(ConfPaths.NET_SOUNDDISTANCE_MAXDISTANCE);
soundDistanceSq = dist * dist;
}
}

View File

@ -0,0 +1,17 @@
package fr.neatmonster.nocheatplus.net;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.WorldConfigCache;
public class NetConfigCache extends WorldConfigCache<NetConfig> {
public NetConfigCache() {
super(true);
}
@Override
protected NetConfig newConfig(String key, ConfigFile configFile) {
return new NetConfig(configFile);
}
}