mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
Make sure that the time we use is always the same across servers
This commit is contained in:
parent
5c76bd8544
commit
cfa2805e00
6 changed files with 179 additions and 11 deletions
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.time;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/*
|
||||
* Thanks:
|
||||
* https://datatracker.ietf.org/doc/html/rfc1769
|
||||
* https://github.com/jonsagara/SimpleNtpClient
|
||||
* https://stackoverflow.com/a/29138806
|
||||
*/
|
||||
public final class SntpClientUtils {
|
||||
private static final int NTP_PORT = 123;
|
||||
|
||||
private static final int NTP_PACKET_SIZE = 48;
|
||||
private static final int NTP_MODE = 3; // client
|
||||
private static final int NTP_VERSION = 3;
|
||||
private static final int RECEIVE_TIME_POSITION = 32;
|
||||
|
||||
private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L;
|
||||
|
||||
public static long requestTimeOffset(String host, int timeout) {
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
socket.setSoTimeout(timeout);
|
||||
|
||||
InetAddress address = InetAddress.getByName(host);
|
||||
|
||||
ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE);
|
||||
|
||||
DatagramPacket request = new DatagramPacket(
|
||||
buff.array(), NTP_PACKET_SIZE, address, NTP_PORT
|
||||
);
|
||||
|
||||
// mode is in the least signification 3 bits
|
||||
// version is in bits 3-5
|
||||
buff.put((byte) (NTP_MODE | (NTP_VERSION << 3)));
|
||||
|
||||
long originateTime = System.currentTimeMillis();
|
||||
socket.send(request);
|
||||
|
||||
DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE);
|
||||
socket.receive(response);
|
||||
|
||||
long responseTime = System.currentTimeMillis();
|
||||
|
||||
// everything before isn't important for us
|
||||
buff.position(RECEIVE_TIME_POSITION);
|
||||
|
||||
long receiveTime = readTimestamp(buff);
|
||||
long transmitTime = readTimestamp(buff);
|
||||
|
||||
return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
private static long readTimestamp(ByteBuffer buffer) {
|
||||
//todo look into the ntp 2036 problem
|
||||
long seconds = buffer.getInt() & 0xffffffffL;
|
||||
long fraction = buffer.getInt() & 0xffffffffL;
|
||||
return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.time;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class TimeSyncer {
|
||||
private final ExecutorService executorService;
|
||||
private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset
|
||||
|
||||
public TimeSyncer(String timeServer) {
|
||||
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
|
||||
service.scheduleWithFixedDelay(() -> {
|
||||
// 5 tries to get the time offset
|
||||
for (int i = 0; i < 5; i++) {
|
||||
long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000);
|
||||
if (offset != Long.MIN_VALUE) {
|
||||
timeOffset = offset;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, 0, 30, TimeUnit.MINUTES);
|
||||
executorService = service;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
public long getTimeOffset() {
|
||||
return timeOffset;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ package org.geysermc.floodgate.util;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.time.TimeSyncer;
|
||||
|
||||
/**
|
||||
* This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This
|
||||
|
@ -56,20 +57,28 @@ public final class BedrockData implements Cloneable {
|
|||
private final long timestamp;
|
||||
private final int dataLength;
|
||||
|
||||
public static BedrockData of(String version, String username, String xuid, int deviceOs,
|
||||
String languageCode, int uiProfile, int inputMode, String ip,
|
||||
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
|
||||
String verifyCode) {
|
||||
public static BedrockData of(
|
||||
String version, String username, String xuid, int deviceOs,
|
||||
String languageCode, int uiProfile, int inputMode, String ip,
|
||||
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
|
||||
String verifyCode, TimeSyncer timeSyncer) {
|
||||
|
||||
long realMillis = System.currentTimeMillis();
|
||||
if (timeSyncer.getTimeOffset() != Long.MIN_VALUE) {
|
||||
realMillis += timeSyncer.getTimeOffset();
|
||||
}
|
||||
|
||||
return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode,
|
||||
uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode,
|
||||
System.currentTimeMillis(), EXPECTED_LENGTH);
|
||||
realMillis, EXPECTED_LENGTH);
|
||||
}
|
||||
|
||||
public static BedrockData of(String version, String username, String xuid, int deviceOs,
|
||||
String languageCode, int uiProfile, int inputMode, String ip,
|
||||
int subscribeId, String verifyCode) {
|
||||
public static BedrockData of(
|
||||
String version, String username, String xuid, int deviceOs,
|
||||
String languageCode, int uiProfile, int inputMode, String ip,
|
||||
int subscribeId, String verifyCode, TimeSyncer timeSyncer) {
|
||||
return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null,
|
||||
false, subscribeId, verifyCode);
|
||||
false, subscribeId, verifyCode, timeSyncer);
|
||||
}
|
||||
|
||||
public static BedrockData fromString(String data) {
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.geysermc.floodgate.crypto.AesCipher;
|
|||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.time.TimeSyncer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import javax.naming.directory.Attribute;
|
||||
|
@ -79,7 +80,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
@Getter
|
||||
public class GeyserConnector {
|
||||
|
||||
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
|
||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||
|
@ -106,6 +106,7 @@ public class GeyserConnector {
|
|||
@Setter
|
||||
private AuthType defaultAuthType;
|
||||
|
||||
private TimeSyncer timeSyncer;
|
||||
private FloodgateCipher cipher;
|
||||
private FloodgateSkinUploader skinUploader;
|
||||
|
||||
|
@ -198,6 +199,7 @@ public class GeyserConnector {
|
|||
defaultAuthType = AuthType.getByName(config.getRemote().getAuthType());
|
||||
|
||||
if (defaultAuthType == AuthType.FLOODGATE) {
|
||||
timeSyncer = new TimeSyncer(Constants.NTP_SERVER);
|
||||
try {
|
||||
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
|
||||
cipher = new AesCipher(new Base64Topping());
|
||||
|
@ -353,6 +355,7 @@ public class GeyserConnector {
|
|||
|
||||
generalThreadPool.shutdown();
|
||||
bedrockServer.close();
|
||||
timeSyncer.shutdown();
|
||||
players.clear();
|
||||
defaultAuthType = null;
|
||||
this.getCommandManager().getCommands().clear();
|
||||
|
@ -431,6 +434,10 @@ public class GeyserConnector {
|
|||
return bootstrap.getWorldManager();
|
||||
}
|
||||
|
||||
public TimeSyncer getTimeSyncer() {
|
||||
return timeSyncer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use XML reflections in the jar or manually find the reflections.
|
||||
* Will return true if the version number is not 'DEV' and the platform is not Fabric.
|
||||
|
|
|
@ -691,7 +691,8 @@ public class GeyserSession implements CommandSender {
|
|||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getAddress().getAddress().getHostAddress(),
|
||||
skinUploader.getId(),
|
||||
skinUploader.getVerifyCode()
|
||||
skinUploader.getVerifyCode(),
|
||||
connector.getTimeSyncer()
|
||||
).toString());
|
||||
} catch (Exception e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.net.URISyntaxException;
|
|||
|
||||
public final class Constants {
|
||||
public static final URI SKIN_UPLOAD_URI;
|
||||
public static final String NTP_SERVER = "time.cloudflare.com";
|
||||
|
||||
static {
|
||||
URI skinUploadUri = null;
|
||||
|
|
Loading…
Reference in a new issue