Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5cd43f2fa | ||
|
|
d69d4f7ba8 | ||
|
|
52325a8a2c | ||
|
|
b07fe80979 | ||
|
|
c6471d8e15 | ||
|
|
0dd3c25421 | ||
|
|
42530caa57 | ||
|
|
e58319cabe | ||
|
|
1de480d575 | ||
|
|
1959106ac8 | ||
|
|
5750391e43 | ||
|
|
00f50110fe |
No files matched your search
@@ -11,7 +11,7 @@ TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
|
||||
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
|
||||
|
||||
### Setup
|
||||
Setting up the plugin is easy as cake. Please follow [these](https://docs.tcpshield.com/onboarding/tcpshield-plugin) guidelines.
|
||||
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/onboarding/tcpshield-plugin) guidelines.
|
||||
|
||||
### Compiling
|
||||
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ configurations {
|
||||
testImplementation.extendsFrom compileOnly
|
||||
}
|
||||
|
||||
version = '2.3'
|
||||
version = '2.4'
|
||||
group = 'net.tcpshield.tcpshield'
|
||||
archivesBaseName = 'TCPShield'
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.tcpshield.tcpshield;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static long MAX_TIME_DIFFERENCE = 3; // in seconds
|
||||
|
||||
}
|
||||
@@ -4,23 +4,22 @@ import net.tcpshield.tcpshield.abstraction.IPacket;
|
||||
import net.tcpshield.tcpshield.abstraction.IPlayer;
|
||||
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
import net.tcpshield.tcpshield.exception.*;
|
||||
import net.tcpshield.tcpshield.validation.TimestampValidation;
|
||||
import net.tcpshield.tcpshield.validation.IPValidation;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class HandshakePacketHandler {
|
||||
|
||||
private final Logger logger;
|
||||
private final SignatureVerifier signatureVerifier;
|
||||
private final TimestampValidation timestampValidation;
|
||||
private final IPValidation ipValidation;
|
||||
private final TCPShieldConfig config;
|
||||
|
||||
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
|
||||
try {
|
||||
this.logger = logger;
|
||||
this.signatureVerifier = new SignatureVerifier();
|
||||
this.timestampValidation = new TimestampValidation(config);
|
||||
this.ipValidation = new IPValidation(config, logger);
|
||||
this.config = config;
|
||||
} catch (Exception e) {
|
||||
throw new TCPShieldInitializationException(e);
|
||||
@@ -31,6 +30,12 @@ public class HandshakePacketHandler {
|
||||
String rawPayload = packet.getRawPayload();
|
||||
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(player.getIP());
|
||||
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
|
||||
|
||||
if (rawPayload == null)
|
||||
return; // raw payload is passed when it's not available -> e.g. Paper server list ping events
|
||||
|
||||
String extraData = null;
|
||||
|
||||
// fix for e.g. incoming FML tagged packets
|
||||
@@ -44,41 +49,24 @@ public class HandshakePacketHandler {
|
||||
}
|
||||
|
||||
String[] payload = cleanedPayload.split("///", 4);
|
||||
if (payload.length != 4)
|
||||
throw new MalformedPayloadException("payload.length != 4. Raw payload = \"" + rawPayload + "\"");
|
||||
if (payload.length < 2)
|
||||
throw new MalformedPayloadException("payload.length < 2. Raw payload = \"" + rawPayload + "\"");
|
||||
|
||||
String hostname = payload[0];
|
||||
String ipData = payload[1];
|
||||
int timestamp;
|
||||
try {
|
||||
timestamp = Integer.parseInt(payload[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new MalformedPayloadException(e);
|
||||
}
|
||||
String signature = payload[3];
|
||||
|
||||
if (!timestampValidation.checkTimestamp(timestamp))
|
||||
throw new InvalidTimestampException(timestamp, timestampValidation.getTime());
|
||||
|
||||
String[] hostnameParts = ipData.split(":");
|
||||
String host = hostnameParts[0];
|
||||
int port = Integer.parseInt(hostnameParts[1]);
|
||||
|
||||
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
|
||||
|
||||
if (!signatureVerifier.verify(reconstructedPayload, signature))
|
||||
throw new SigningVerificationFailureException();
|
||||
|
||||
InetSocketAddress newIP = new InetSocketAddress(host, port);
|
||||
player.setIP(newIP);
|
||||
|
||||
if (extraData != null) hostname = hostname + extraData;
|
||||
|
||||
packet.modifyOriginalPacket(hostname);
|
||||
} catch (InvalidTimestampException e) {
|
||||
handleInvalidTimestamp(player, e.getTimestamp(), e.getCurrentTime());
|
||||
} catch (SigningVerificationFailureException e) {
|
||||
handleSigningVerificationFailure(player, rawPayload);
|
||||
} catch (InvalidIPException e) {
|
||||
handleInvalidIPException(player, rawPayload);
|
||||
} catch (ConnectionNotProxiedException e) {
|
||||
handleNotProxiedConnection(player, rawPayload);
|
||||
} catch (IPModificationFailureException e) {
|
||||
@@ -89,20 +77,9 @@ public class HandshakePacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInvalidTimestamp(IPlayer player, long timestamp, long currentTime) {
|
||||
private void handleInvalidIPException(IPlayer player, String rawPayload) {
|
||||
if (config.isDebug()) {
|
||||
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but timestamp was not valid. " +
|
||||
"Provided timestamp: %d vs. system timestamp: %d. Please check your machine time. Timestamp validation mode: %s", player.getName(), player.getUUID(), player.getIP(), timestamp, currentTime, config.getTimestampValidationMode()));
|
||||
}
|
||||
|
||||
if (config.isOnlyProxy()) {
|
||||
player.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSigningVerificationFailure(IPlayer player, String rawPayload) {
|
||||
if (config.isDebug()) {
|
||||
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
|
||||
this.logger.warning(String.format("%s[%s/%s] failed the ip check. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
|
||||
}
|
||||
|
||||
if (config.isOnlyProxy()) {
|
||||
|
||||
@@ -73,9 +73,9 @@ public class ReflectionUtils {
|
||||
if (cachedField != null) return cachedField;
|
||||
|
||||
Class<?> currentClass = clazz;
|
||||
do {
|
||||
do {
|
||||
for (Field field : currentClass.getDeclaredFields()) {
|
||||
if (field.getType() != searchFor) continue;
|
||||
if (!searchFor.isAssignableFrom(field.getType())) continue;
|
||||
|
||||
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
|
||||
return field;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package net.tcpshield.tcpshield;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SignatureVerifier {
|
||||
|
||||
private final PublicKey publicKey;
|
||||
|
||||
public SignatureVerifier() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
byte[] encodedKey = ByteStreams.toByteArray(SignatureVerifier.class.getResourceAsStream("/signing_pub.key"));
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("EC");
|
||||
publicKey = keyFactory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
public boolean verify(String str, String signature) {
|
||||
return verify(str.getBytes(StandardCharsets.UTF_8), signature);
|
||||
}
|
||||
|
||||
private boolean verify(byte[] data, String signature) {
|
||||
try {
|
||||
byte[] decodedSignature = Base64.getDecoder().decode(signature);
|
||||
|
||||
Signature sig = Signature.getInstance("SHA512withECDSA");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data);
|
||||
|
||||
return sig.verify(decodedSignature);
|
||||
} catch (IllegalArgumentException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) { // catching for illegal base64 encoding or invalid encoding for signatures
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package net.tcpshield.tcpshield.abstraction;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class TCPShieldConfig {
|
||||
|
||||
protected boolean onlyProxy;
|
||||
protected String timestampValidationMode;
|
||||
protected File ipWhitelistFolder;
|
||||
protected boolean debug;
|
||||
|
||||
public boolean isOnlyProxy() {
|
||||
return this.onlyProxy;
|
||||
}
|
||||
|
||||
public String getTimestampValidationMode() {
|
||||
return this.timestampValidationMode;
|
||||
public File getIPWhitelistFolder() {
|
||||
return ipWhitelistFolder;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
|
||||
@@ -13,8 +13,7 @@ public class TCPShieldBukkit extends JavaPlugin {
|
||||
new TCPShieldPaper(this).load();
|
||||
} else {
|
||||
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
|
||||
//this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
|
||||
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Please install ProtocolLib in order to use our plugin.");
|
||||
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,7 +22,7 @@ public class TCPShieldBukkit extends JavaPlugin {
|
||||
}
|
||||
|
||||
private boolean isPaper() {
|
||||
if (true) return false; // disabled for now
|
||||
if (1 == 1) return false;
|
||||
|
||||
try {
|
||||
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
|
||||
|
||||
@@ -4,6 +4,8 @@ import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class BukkitConfigImpl extends TCPShieldConfig {
|
||||
|
||||
public BukkitConfigImpl(JavaPlugin javaPlugin) {
|
||||
@@ -12,7 +14,7 @@ public class BukkitConfigImpl extends TCPShieldConfig {
|
||||
FileConfiguration config = javaPlugin.getConfig();
|
||||
|
||||
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
|
||||
this.timestampValidationMode = config.getString("timestamp-validation");
|
||||
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
|
||||
this.debug = config.getBoolean("debug-mode");
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import net.tcpshield.tcpshield.abstraction.IPlayer;
|
||||
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
|
||||
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
|
||||
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
|
||||
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
|
||||
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -32,5 +34,10 @@ public class PaperHandshakePacketHandler implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onServerPing(PaperServerListPingEvent e) {
|
||||
if (e.getClient().isLegacy()) e.setCancelled(true);
|
||||
|
||||
IPacket packet = new PaperServerListPacketImpl(e);
|
||||
IPlayer player = new PaperServerListPingPlayerImpl(e);
|
||||
|
||||
handshakePacketHandler.onHandshake(packet, player);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package net.tcpshield.tcpshield.bukkit.paper.impl;
|
||||
|
||||
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
|
||||
import net.tcpshield.tcpshield.abstraction.IPacket;
|
||||
|
||||
public class PaperServerListPacketImpl implements IPacket {
|
||||
|
||||
private final PaperServerListPingEvent pingEvent;
|
||||
|
||||
public PaperServerListPacketImpl(PaperServerListPingEvent pingEvent) {
|
||||
this.pingEvent = pingEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRawPayload() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyOriginalPacket(String hostname) {
|
||||
pingEvent.setCancelled(false);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package net.tcpshield.tcpshield.bukkit.paper.impl;
|
||||
|
||||
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
|
||||
import net.tcpshield.tcpshield.abstraction.IPlayer;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class PaperServerListPingPlayerImpl implements IPlayer {
|
||||
|
||||
private final PaperServerListPingEvent pingEvent;
|
||||
|
||||
public PaperServerListPingPlayerImpl(PaperServerListPingEvent pingEvent) {
|
||||
this.pingEvent = pingEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUUID() {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIP() {
|
||||
return pingEvent.getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIP(InetSocketAddress ip) {
|
||||
// not necessary since this class is only for server list pings
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
pingEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class BungeeConfigImpl extends TCPShieldConfig {
|
||||
|
||||
Configuration config = loadConfig();
|
||||
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
|
||||
this.timestampValidationMode = config.getString("timestamp-validation");
|
||||
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
|
||||
this.debug = config.getBoolean("debug-mode");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.tcpshield.tcpshield.exception;
|
||||
|
||||
public class InvalidIPException extends ConnectionNotProxiedException {
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package net.tcpshield.tcpshield.exception;
|
||||
|
||||
public class InvalidTimestampException extends SigningVerificationFailureException {
|
||||
|
||||
private final long timestamp;
|
||||
private final long currentTime;
|
||||
|
||||
public InvalidTimestampException(long timestamp, long currentTime) {
|
||||
this.timestamp = timestamp;
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public long getCurrentTime() {
|
||||
return currentTime;
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
package net.tcpshield.tcpshield.exception;
|
||||
|
||||
public class SigningVerificationFailureException extends MalformedPayloadException {
|
||||
|
||||
public SigningVerificationFailureException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.tcpshield.tcpshield.exception;
|
||||
|
||||
public class TCPShieldIPListUpdaterException extends RuntimeException {
|
||||
|
||||
public TCPShieldIPListUpdaterException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.tcpshield.tcpshield.validation;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class IPListAutoUpdater {
|
||||
|
||||
private final IPListUpdater ipListUpdater;
|
||||
|
||||
public IPListAutoUpdater(IPListUpdater ipListUpdater) {
|
||||
this.ipListUpdater = ipListUpdater;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
ipListUpdater.update();
|
||||
}
|
||||
}, TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(12));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.tcpshield.tcpshield.validation;
|
||||
|
||||
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
import net.tcpshield.tcpshield.exception.TCPShieldIPListUpdaterException;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IPListUpdater {
|
||||
|
||||
private final TCPShieldConfig config;
|
||||
private final IPValidation ipValidation;
|
||||
private final Logger logger;
|
||||
|
||||
public IPListUpdater(TCPShieldConfig config, IPValidation ipValidation, Logger logger) {
|
||||
this.config = config;
|
||||
this.ipValidation = ipValidation;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
try {
|
||||
String ipList = getIPList();
|
||||
|
||||
File ipWhitelistFolder = config.getIPWhitelistFolder();
|
||||
File tcpShieldIPList = new File(ipWhitelistFolder, "tcpshield-ips.list");
|
||||
|
||||
if (tcpShieldIPList.createNewFile()) {
|
||||
logger.info("[TCPShield] Fetched updated IP Whitelist from https://tcpshield.com/v4/");
|
||||
}
|
||||
|
||||
try (FileWriter writer = new FileWriter(tcpShieldIPList, false)) {
|
||||
writer.write(ipList);
|
||||
}
|
||||
|
||||
ipValidation.readIPWhitelist();
|
||||
} catch (Exception e) {
|
||||
throw new TCPShieldIPListUpdaterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getIPList() throws IOException {
|
||||
URL url = new URL("https://tcpshield.com/v4/");
|
||||
|
||||
HttpsURLConnection connection = null;
|
||||
try {
|
||||
connection = (HttpsURLConnection) url.openConnection();
|
||||
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
return bufferedReader.lines().filter(str -> !str.isEmpty()).collect(Collectors.joining("\n"));
|
||||
}
|
||||
} finally {
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.tcpshield.tcpshield.validation;
|
||||
|
||||
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
|
||||
import net.tcpshield.tcpshield.validation.cidr.CIDRChecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class IPValidation {
|
||||
|
||||
private final TCPShieldConfig config;
|
||||
private final IPListUpdater ipListUpdater;
|
||||
private final CIDRChecker cidrChecker;
|
||||
|
||||
private List<String> ipWhitelist = new ArrayList<>();
|
||||
|
||||
public IPValidation(TCPShieldConfig config, Logger logger) {
|
||||
this.config = config;
|
||||
this.ipListUpdater = new IPListUpdater(config, this, logger);
|
||||
new IPListAutoUpdater(ipListUpdater).init();
|
||||
|
||||
checkIPList();
|
||||
|
||||
try {
|
||||
readIPWhitelist();
|
||||
this.cidrChecker = new CIDRChecker(ipWhitelist);
|
||||
} catch (IOException e) {
|
||||
throw new TCPShieldInitializationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateIP(InetAddress inetAddress) {
|
||||
return cidrChecker.check(inetAddress);
|
||||
}
|
||||
|
||||
private List<String> getIPWhitelist() throws IOException {
|
||||
List<String> ipWhitelist = new ArrayList<>();
|
||||
|
||||
for (File file : Objects.requireNonNull(config.getIPWhitelistFolder().listFiles())) {
|
||||
if (file.isDirectory()) continue;
|
||||
|
||||
try (Scanner scanner = new Scanner(file)) {
|
||||
while (scanner.hasNextLine()) {
|
||||
String cidrEntry = scanner.nextLine();
|
||||
ipWhitelist.add(cidrEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ipWhitelist;
|
||||
}
|
||||
|
||||
public void readIPWhitelist() throws IOException {
|
||||
this.ipWhitelist = getIPWhitelist();
|
||||
}
|
||||
|
||||
private void checkIPList() {
|
||||
if (config.getIPWhitelistFolder().mkdirs()) {
|
||||
ipListUpdater.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package net.tcpshield.tcpshield.validation;
|
||||
|
||||
public interface ITimestampValidator {
|
||||
|
||||
boolean validateTimestamp(long checkedTimestamp);
|
||||
|
||||
default long getTime() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package net.tcpshield.tcpshield.validation;
|
||||
|
||||
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
import net.tcpshield.tcpshield.validation.impl.DisabledTimestampValidatorImpl;
|
||||
import net.tcpshield.tcpshield.validation.impl.HTPDateTimestampValidatorImpl;
|
||||
import net.tcpshield.tcpshield.validation.impl.SystemTimeTimestampValidatorImpl;
|
||||
|
||||
public class TimestampValidation {
|
||||
|
||||
private final ITimestampValidator timestampValidator;
|
||||
|
||||
public TimestampValidation(TCPShieldConfig config) {
|
||||
switch (config.getTimestampValidationMode().toLowerCase()) {
|
||||
case "system":
|
||||
this.timestampValidator = new SystemTimeTimestampValidatorImpl();
|
||||
break;
|
||||
case "off":
|
||||
this.timestampValidator = new DisabledTimestampValidatorImpl();
|
||||
break;
|
||||
case "htpdate":
|
||||
default:
|
||||
this.timestampValidator = new HTPDateTimestampValidatorImpl();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkTimestamp(long timestamp) {
|
||||
return timestampValidator.validateTimestamp(timestamp);
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return timestampValidator.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.tcpshield.tcpshield.validation.cidr;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class CIDRChecker {
|
||||
|
||||
private final List<CIDRMatcher> cidrMatchers = new ArrayList<>();
|
||||
private final Set<String> cache = new HashSet<>(); // quick cache for already verified IPs; HashSet is O(1) on HashSet#contains() operations
|
||||
|
||||
public CIDRChecker(List<String> cidrList) {
|
||||
for (String cidr : cidrList) {
|
||||
CIDRMatcher cidrMatcher = new CIDRMatcher(cidr);
|
||||
cidrMatchers.add(cidrMatcher);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean check(InetAddress inetAddress) {
|
||||
String ip = inetAddress.getHostAddress();
|
||||
if (cache.contains(ip)) return true;
|
||||
|
||||
if (!checkUncached(inetAddress)) return false;
|
||||
|
||||
cache.add(ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkUncached(InetAddress inetAddress) {
|
||||
for (CIDRMatcher cidrMatcher : cidrMatchers) {
|
||||
if (cidrMatcher.matches(inetAddress)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package net.tcpshield.tcpshield.validation.cidr;
|
||||
|
||||
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class CIDRMatcher {
|
||||
|
||||
private final int maskBits;
|
||||
private final int maskBytes;
|
||||
private final boolean simpleCIDR;
|
||||
private final InetAddress cidrAddress;
|
||||
|
||||
public CIDRMatcher(String ipAddress) {
|
||||
String[] split = ipAddress.split("/");
|
||||
|
||||
String parsedIPAddress;
|
||||
if (split.length != 0) {
|
||||
parsedIPAddress = split[0];
|
||||
|
||||
this.maskBits = Integer.parseInt(split[1]);
|
||||
this.simpleCIDR = maskBits == 32;
|
||||
} else {
|
||||
parsedIPAddress = ipAddress;
|
||||
|
||||
this.maskBits = -1;
|
||||
this.simpleCIDR = true;
|
||||
}
|
||||
|
||||
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
|
||||
|
||||
try {
|
||||
cidrAddress = InetAddress.getByName(parsedIPAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new TCPShieldInitializationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(InetAddress inetAddress) {
|
||||
if (!cidrAddress.getClass().equals(inetAddress.getClass())) return false; // check if IP is IPv4 or IPv6
|
||||
|
||||
if (simpleCIDR) return inetAddress.equals(cidrAddress); // check for equality if it's a simple CIDR
|
||||
|
||||
byte[] inetAddressBytes = inetAddress.getAddress();
|
||||
byte[] requiredAddressBytes = cidrAddress.getAddress();
|
||||
|
||||
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
|
||||
|
||||
for (int i = 0; i < maskBytes; i++) {
|
||||
if (inetAddressBytes[i] != requiredAddressBytes[i]) return false;
|
||||
}
|
||||
|
||||
if (finalByte != 0)
|
||||
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package net.tcpshield.tcpshield.validation.impl;
|
||||
|
||||
import net.tcpshield.tcpshield.validation.ITimestampValidator;
|
||||
|
||||
/**
|
||||
* @author Fuzzlemann
|
||||
*/
|
||||
public class DisabledTimestampValidatorImpl implements ITimestampValidator {
|
||||
@Override
|
||||
public boolean validateTimestamp(long checkedTimestamp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
package net.tcpshield.tcpshield.validation.impl;
|
||||
|
||||
import net.tcpshield.tcpshield.Constants;
|
||||
import net.tcpshield.tcpshield.validation.ITimestampValidator;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* inspired by https://github.com/iridium77/htpdate/blob/master/htpdate.c
|
||||
*/
|
||||
public class HTPDateTimestampValidatorImpl implements ITimestampValidator {
|
||||
|
||||
private volatile long timestampOffset = 0;
|
||||
|
||||
public HTPDateTimestampValidatorImpl() {
|
||||
ForkJoinPool.commonPool().submit(() -> {
|
||||
try {
|
||||
syncTimestamp();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}); // do not execute network requests on the main thread
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateTimestamp(long checkedTimestamp) {
|
||||
long currentTime = getTime();
|
||||
|
||||
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
|
||||
}
|
||||
|
||||
private void syncTimestamp() throws IOException {
|
||||
try (Socket socket = new Socket("google.com", 80)) {
|
||||
// no-cache forces the server to return time
|
||||
String payload = "HEAD http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: tcpshield/1.0\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n";
|
||||
socket.getOutputStream().write(payload.getBytes());
|
||||
|
||||
long readTime = System.currentTimeMillis(); // assuming server -> client time is negligible
|
||||
|
||||
List<String> response = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Date serverDate = extractDate(response);
|
||||
|
||||
timestampOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
|
||||
}
|
||||
}
|
||||
|
||||
private Date extractDate(List<String> response) {
|
||||
for (String line : response) {
|
||||
if (!line.startsWith("Date: ")) continue;
|
||||
|
||||
String dateString = line.substring("Date: ".length());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
|
||||
try {
|
||||
return sdf.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("no date line found - response: " + response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return (System.currentTimeMillis() + timestampOffset) / 1000;
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package net.tcpshield.tcpshield.validation.impl;
|
||||
|
||||
import net.tcpshield.tcpshield.Constants;
|
||||
import net.tcpshield.tcpshield.validation.ITimestampValidator;
|
||||
|
||||
public class SystemTimeTimestampValidatorImpl implements ITimestampValidator {
|
||||
|
||||
@Override
|
||||
public boolean validateTimestamp(long checkedTimestamp) {
|
||||
long currentTime = getTime();
|
||||
|
||||
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class VelocityConfigImpl extends TCPShieldConfig {
|
||||
saveDefaultConfig();
|
||||
Toml toml = loadConfig();
|
||||
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
|
||||
this.timestampValidationMode = toml.getString("timestamp-validation");
|
||||
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
|
||||
this.debug = toml.getBoolean("debug-mode");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: TCPShield
|
||||
version: '2.3'
|
||||
version: '2.4'
|
||||
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
|
||||
author: https://tcpshield.com
|
||||
@@ -1,8 +1,5 @@
|
||||
# Please keep this as true in while using TCPShield!
|
||||
only-allow-proxy-connections = true
|
||||
|
||||
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
|
||||
timestamp-validation = 'htpdate'
|
||||
|
||||
# Turn on to diagnose connection issues
|
||||
debug-mode = false
|
||||
@@ -1,8 +1,5 @@
|
||||
# Please keep this as true in while using TCPShield!
|
||||
only-allow-proxy-connections: true
|
||||
|
||||
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
|
||||
timestamp-validation: 'htpdate'
|
||||
|
||||
# Turn on to diagnose connection issues
|
||||
debug-mode: false
|
||||
@@ -1,5 +1,5 @@
|
||||
name: TCPShield
|
||||
version: '2.3'
|
||||
version: '2.4'
|
||||
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
|
||||
softdepend:
|
||||
- ProtocolLib
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "tcpshield",
|
||||
"name": "TCPShield",
|
||||
"version": "2.3",
|
||||
"version": "2.4",
|
||||
"description": "TCPShield IP parsing capabilities for Velocity",
|
||||
"authors": [
|
||||
"TCPShield"
|
||||
|
||||
@@ -9,27 +9,25 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
public class HandshakePacketHandlerTest {
|
||||
|
||||
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl("system");
|
||||
private final TCPShieldConfig skippingTimestampCheckTestConfig = new TestConfigImpl("off");
|
||||
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl();
|
||||
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
|
||||
private final HandshakePacketHandler skippingTimestampCheckHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), skippingTimestampCheckTestConfig);
|
||||
private TestPlayerImpl player;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
player = new TestPlayerImpl();
|
||||
player = new TestPlayerImpl(TestConstants.VALID_IP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulHandshake() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
|
||||
|
||||
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
|
||||
standardHandshakePacketHandler.onHandshake(packet, player);
|
||||
|
||||
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
|
||||
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
|
||||
@@ -39,15 +37,15 @@ public class HandshakePacketHandlerTest {
|
||||
public void testSuccessfulFMLTaggedHandshake() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
|
||||
|
||||
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
|
||||
standardHandshakePacketHandler.onHandshake(packet, player);
|
||||
|
||||
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
|
||||
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsuccessfulHandshakeFailedTimestampCheck() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
|
||||
public void testUnsuccessfulHandshakeMalformedPayload() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
|
||||
|
||||
standardHandshakePacketHandler.onHandshake(packet, player);
|
||||
|
||||
@@ -55,19 +53,11 @@ public class HandshakePacketHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsuccessfulHandshakeMalformedPayload() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
|
||||
public void testUnsuccessfulHandshakeInvalidIP() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
|
||||
|
||||
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
|
||||
|
||||
assertFalse(player.isConnected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsuccessfulHandshakeInvalidSignature() {
|
||||
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE, TestConstants.STANDARD_HOSTNAME);
|
||||
|
||||
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
|
||||
TestPlayerImpl player = new TestPlayerImpl(TestConstants.INVALID_IP);
|
||||
standardHandshakePacketHandler.onHandshake(packet, player);
|
||||
|
||||
assertFalse(player.isConnected());
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package net.tcpshield.tcpshield;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SignatureObjectVerifierTest {
|
||||
|
||||
private static SignatureVerifier signatureVerifier;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
|
||||
signatureVerifier = new SignatureVerifier();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerificationSuccess() {
|
||||
SignatureObject signatureObject = createSignature(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP);
|
||||
assertTrue(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerificationFailure() {
|
||||
SignatureObject signatureObject = createSignature(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE);
|
||||
assertFalse(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
|
||||
}
|
||||
|
||||
private SignatureObject createSignature(String payload) {
|
||||
int signatureIndex = payload.lastIndexOf("///");
|
||||
String signature = payload.substring(signatureIndex + 3);
|
||||
String reconstructedPayload = payload.substring(0, signatureIndex);
|
||||
|
||||
return new SignatureObject(signature, reconstructedPayload);
|
||||
}
|
||||
|
||||
private static class SignatureObject {
|
||||
|
||||
private final String signature;
|
||||
private final String reconstructedPayload;
|
||||
|
||||
private SignatureObject(String signature, String reconstructedPayload) {
|
||||
this.signature = signature;
|
||||
this.reconstructedPayload = reconstructedPayload;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public String getReconstructedPayload() {
|
||||
return reconstructedPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,15 @@ public class TestConstants {
|
||||
|
||||
public static final String STANDARD_HOSTNAME = "example.org";
|
||||
|
||||
public static final String VALID_IP = "51.161.19.224";
|
||||
public static final String INVALID_IP = "1.1.1.1";
|
||||
|
||||
public static final String PAYLOAD_HOSTNAME = "homeserver.fuzzlemann.de";
|
||||
public static final String PAYLOAD_FML_HOSTNAME = "homeserver.fuzzlemann.de\0FML\0";
|
||||
public static final String PAYLOAD_IP = "79.227.33.179";
|
||||
|
||||
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
|
||||
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD_INVALID_TIMESTAMP + "\0FML\0";
|
||||
public static final String INVALID_PAYLOAD_INVALID_SIGNATURE = VALID_PAYLOAD_INVALID_TIMESTAMP + "_INVALID_SIGNATURE";
|
||||
public static final String VALID_PAYLOAD = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
|
||||
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD + "\0FML\0";
|
||||
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "MALFORMED_PAYLOAD";
|
||||
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package net.tcpshield.tcpshield.impl;
|
||||
|
||||
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class TestConfigImpl extends TCPShieldConfig {
|
||||
|
||||
public TestConfigImpl(String timestampValidationMode) {
|
||||
this.timestampValidationMode = timestampValidationMode;
|
||||
public TestConfigImpl() {
|
||||
this.ipWhitelistFolder = new File("ip-whitelist");
|
||||
this.debug = true;
|
||||
this.onlyProxy = true;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,22 @@ package net.tcpshield.tcpshield.impl;
|
||||
|
||||
import net.tcpshield.tcpshield.abstraction.IPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class TestPlayerImpl implements IPlayer {
|
||||
|
||||
private InetSocketAddress ip;
|
||||
private InetAddress ip;
|
||||
private boolean connected;
|
||||
|
||||
public TestPlayerImpl() {
|
||||
public TestPlayerImpl(String ip) {
|
||||
this.connected = true;
|
||||
try {
|
||||
this.ip = InetAddress.getByName(ip);
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,12 +34,12 @@ public class TestPlayerImpl implements IPlayer {
|
||||
public String getIP() {
|
||||
if (ip == null) return "ip";
|
||||
|
||||
return ip.getAddress().getHostAddress();
|
||||
return ip.getHostAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIP(InetSocketAddress ip) {
|
||||
this.ip = ip;
|
||||
this.ip = ip.getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in new issue
Block a user