Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Freesia-Backend/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name: Freesia-Backend
version: 'v1.0.2_YSM_2.6.2'
version: '1.0.3-YSM-2.6.4'
main: com.nguyendevs.freesia.backend.FreesiaBackend
api-version: '1.16'

folia-supported: true
2 changes: 2 additions & 0 deletions Freesia-Common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ dependencies {
compileOnly("org.slf4j:slf4j-api:2.0.13")
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("ca.spottedleaf:concurrentutil:0.0.3")
compileOnly("org.bouncycastle:bcpkix-jdk18on:1.78.1")
compileOnly("org.bouncycastle:bcprov-jdk18on:1.78.1")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.nguyendevs.freesia.common;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.File;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;

public class ServerSslUtils {
public static SslContext createServerContext(boolean useSelfSigned, String certPath, String keyPath) throws Exception {
if (useSelfSigned) {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}

KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(2048, new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();

X500Name issuer = new X500Name("CN=Freesia");
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
Date notBefore = new Date(System.currentTimeMillis() - 86400000L);
Date notAfter = new Date(System.currentTimeMillis() + 86400000L * 365);

X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
issuer, serial, notBefore, notAfter, issuer, kp.getPublic()
);
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(kp.getPrivate());
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));

EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Generated Self-Signed Certificate for Master Server\u001B[0m");
return SslContextBuilder.forServer(kp.getPrivate(), cert).build();
} else {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Loaded Certificate from " + certPath + " for Master Server\u001B[0m");
return SslContextBuilder.forServer(new File(certPath), new File(keyPath)).build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nguyendevs.freesia.common;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

import java.io.File;

public class SslUtils {

public static SslContext createClientContext(boolean trustAll, String trustCertPath) throws Exception {
if (trustAll) {
EntryPoint.LOGGER_INST.info("\u001B[33m[Security] Client trusting all certificates (Insecure TrustManager)\u001B[0m");
return SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Client loading TrustStore from " + trustCertPath + "\u001B[0m");
return SslContextBuilder.forClient().trustManager(new File(trustCertPath)).build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@

public class DefaultChannelPipelineLoader {

public static void loadDefaultHandlers(@NotNull Channel channel) {
public static void loadDefaultHandlers(@NotNull Channel channel, io.netty.handler.ssl.SslContext sslContext, com.nguyendevs.freesia.common.communicating.FreesiaIpFilterHandler ipFilterHandler) {
if (ipFilterHandler != null) {
channel.pipeline().addFirst("firewall", ipFilterHandler);
}
if (sslContext != null) {
channel.pipeline().addLast(sslContext.newHandler(channel.alloc()));
}
channel.pipeline()
.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
.addLast(new LengthFieldPrepender(4))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.nguyendevs.freesia.common.communicating;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.ipfilter.AbstractRemoteAddressFilter;
import com.nguyendevs.freesia.common.EntryPoint;
import io.netty.channel.ChannelHandler;
import java.net.InetSocketAddress;
import java.util.List;

@ChannelHandler.Sharable
public class FreesiaIpFilterHandler extends AbstractRemoteAddressFilter<InetSocketAddress> {
private final boolean enableIpFilter;
private final List<String> allowedIps;

public FreesiaIpFilterHandler(boolean enableIpFilter, List<String> allowedIps) {
this.enableIpFilter = enableIpFilter;
this.allowedIps = allowedIps;
}

@Override
protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception {
if (!enableIpFilter) {
return true;
}

String ip = remoteAddress.getAddress().getHostAddress();
boolean isWhitelisted = allowedIps != null && allowedIps.contains(ip);

if (!isWhitelisted) {
EntryPoint.LOGGER_INST.warn("\u001B[31m[Security] FIREWALL BLOCKED connection from unauthorized IP: " + ip + "\u001B[0m");
}

return isWhitelisted;
}

@Override
protected ChannelFuture channelRejected(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) {
ChannelFuture rejectFuture = ctx.channel().close();
rejectFuture.addListener(ChannelFutureListener.CLOSE);
return rejectFuture;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ public class NettySocketClient {
private final InetSocketAddress masterAddress;
private final Queue<IMessage<?>> packetFlushQueue = new ConcurrentLinkedQueue<>();
private final Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator;
private final io.netty.handler.ssl.SslContext sslContext;
private final int reconnectInterval;
private volatile Channel channel;
private volatile boolean isConnected = false;

public NettySocketClient(InetSocketAddress masterAddress, Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator, int reconnectInterval) {
public NettySocketClient(InetSocketAddress masterAddress, Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator, int reconnectInterval, io.netty.handler.ssl.SslContext sslContext) {
this.masterAddress = masterAddress;
this.handlerCreator = handlerCreator;
this.reconnectInterval = reconnectInterval;
this.sslContext = sslContext;
}

public void connect() {
Expand All @@ -40,7 +42,7 @@ public void connect() {
.handler(new ChannelInitializer<>() {
@Override
protected void initChannel(@NotNull Channel channel) {
DefaultChannelPipelineLoader.loadDefaultHandlers(channel);
DefaultChannelPipelineLoader.loadDefaultHandlers(channel, NettySocketClient.this.sslContext, null);
channel.pipeline().addLast(NettySocketClient.this.handlerCreator.apply(channel));
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,26 @@ public class NettySocketServer {
private final EventLoopGroup masterLoopGroup = NettyUtils.eventLoopGroup();
private final EventLoopGroup workerLoopGroup = NettyUtils.eventLoopGroup();
private final Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator;
private final io.netty.handler.ssl.SslContext sslContext;
private final com.nguyendevs.freesia.common.communicating.FreesiaIpFilterHandler ipFilterHandler;
private volatile ChannelFuture channelFuture;

public NettySocketServer(InetSocketAddress bindAddress, Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator) {
public NettySocketServer(InetSocketAddress bindAddress, Function<Channel, SimpleChannelInboundHandler<?>> handlerCreator, io.netty.handler.ssl.SslContext sslContext, com.nguyendevs.freesia.common.communicating.FreesiaIpFilterHandler ipFilterHandler) {
this.bindAddress = bindAddress;
this.handlerCreator = handlerCreator;
this.sslContext = sslContext;
this.ipFilterHandler = ipFilterHandler;
}

public void bind() {
this.channelFuture = new ServerBootstrap()
.group(this.masterLoopGroup, this.workerLoopGroup)
.channel(NettyUtils.serverChannelClass())
.option(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(@NotNull Channel channel) {
DefaultChannelPipelineLoader.loadDefaultHandlers(channel);
DefaultChannelPipelineLoader.loadDefaultHandlers(channel, NettySocketServer.this.sslContext, NettySocketServer.this.ipFilterHandler);
channel.pipeline().addLast(NettySocketServer.this.handlerCreator.apply(channel));
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ protected void channelRead0(ChannelHandlerContext ctx, IMessage<NettyClientChann
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof java.io.IOException) {
return;
}
EntryPoint.LOGGER_INST.error("Exception caught in Client channel: ", cause);
}

@Override
public void channelActive(@NotNull ChannelHandlerContext ctx) {
this.getClient().onChannelActive(ctx.channel());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class NettyServerChannelHandlerLayer extends SimpleChannelInboun
@Override
public void channelActive(@NotNull ChannelHandlerContext ctx) {
this.channel = ctx.channel();
EntryPoint.LOGGER_INST.info("Worker connected {}", this.channel);
EntryPoint.LOGGER_INST.info("\u001B[36mWorker connected \u001B[35m{}\u001B[0m", this.channel);
}

@Override
Expand All @@ -32,6 +32,14 @@ protected void channelRead0(ChannelHandlerContext ctx, IMessage<NettyServerChann
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof java.io.IOException) {
return;
}
EntryPoint.LOGGER_INST.error("Exception caught in Server channel: ", cause);
}

public void sendMessage(IMessage<NettyClientChannelHandlerLayer> packet) {
if (!this.channel.isOpen()) {
return;
Expand Down
3 changes: 2 additions & 1 deletion Freesia-Velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ dependencies {
implementation("com.electronwill.night-config:toml:3.6.6")
implementation("org.geysermc.mcprotocollib:protocol:1.21-SNAPSHOT")
implementation("ca.spottedleaf:concurrentutil:0.0.3")
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
}

Expand Down Expand Up @@ -44,7 +46,6 @@ sourceSets.named("main") {
java.srcDir(generateTemplates.map { it.outputs })
}

// 确保 generateTemplates 任务在构建时执行
tasks.named("build") {
dependsOn(generateTemplates)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public void onProxyStart(ProxyInitializeEvent event) {
LOGGER.info("Loading config file and i18n");
try {
FreesiaConfig.init();
FreesiaSecurityConfig.init();
languageManager.loadLanguageFile(FreesiaConfig.languageName);
} catch (IOException e) {
throw new RuntimeException(e);
Expand All @@ -114,7 +115,23 @@ public void onProxyStart(ProxyInitializeEvent event) {

virtualPlayerManager.init();

masterServer = new NettySocketServer(FreesiaConfig.masterServiceAddress, c -> new MasterServerMessageHandler());
io.netty.handler.ssl.SslContext sslContext = null;
try {
if (FreesiaSecurityConfig.enableTls) {
sslContext = com.nguyendevs.freesia.common.ServerSslUtils.createServerContext(
FreesiaSecurityConfig.useSelfSigned,
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.certPath).toFile().getAbsolutePath(),
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.keyPath).toFile().getAbsolutePath()
);
}
} catch (Exception e) {
LOGGER.error("Failed to initialize SSL context!", e);
}
com.nguyendevs.freesia.common.communicating.FreesiaIpFilterHandler ipFilter = new com.nguyendevs.freesia.common.communicating.FreesiaIpFilterHandler(
FreesiaSecurityConfig.enableIpFilter,
FreesiaSecurityConfig.allowedWorkerIps
);
masterServer = new NettySocketServer(FreesiaConfig.masterServiceAddress, c -> new MasterServerMessageHandler(), sslContext, ipFilter);
masterServer.bind();

LOGGER.info("Initiating client kicker.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static final class FileConstants {
public static final File PLUGIN_DIR = new File(PLUGINS_DIR, "Freesia");

public static final File CONFIG_FILE = new File(PLUGIN_DIR, "freesia_config.toml");
public static final File SECURITY_CONFIG_FILE = new File(PLUGIN_DIR, "security.toml");

public static final File PLAYER_DATA_DIR = new File(PLUGIN_DIR, "playerdata");
public static final File VIRTUAL_PLAYER_DATA_DIR = new File(PLUGIN_DIR, "playerdata_virtual");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.nguyendevs.freesia.velocity;

import com.electronwill.nightconfig.core.file.CommentedFileConfig;

import java.io.IOException;

public class FreesiaSecurityConfig {
public static boolean enableTls = false;
public static boolean useSelfSigned = true;
public static String certPath = "cert.pem";
public static String keyPath = "key.pem";
public static boolean enableIpFilter = true;
public static java.util.List<String> allowedWorkerIps = java.util.Arrays.asList("127.0.0.1", "192.168.1.5");

private static CommentedFileConfig CONFIG_INSTANCE;

private static void loadOrDefaultValues() {
enableTls = get("security.enable_tls", enableTls);
useSelfSigned = get("security.use_self_signed", useSelfSigned);
certPath = get("security.cert_path", certPath);
keyPath = get("security.key_path", keyPath);
enableIpFilter = get("firewall.enable_ip_filter", enableIpFilter);
allowedWorkerIps = get("firewall.allowed_worker_ips", allowedWorkerIps);
}

private static <T> T get(String key, T def) {
if (!CONFIG_INSTANCE.contains(key)) {
CONFIG_INSTANCE.add(key, def);
return def;
}

return CONFIG_INSTANCE.get(key);
}

public static void init() throws IOException {
Freesia.LOGGER.info("\u001B[36m[Security] Loading proxy security config.\u001B[0m");

if (!FreesiaConstants.FileConstants.SECURITY_CONFIG_FILE.exists()) {
Freesia.LOGGER.info("\u001B[33m[Security] Security config file not found! Creating new.\u001B[0m");
FreesiaConstants.FileConstants.SECURITY_CONFIG_FILE.createNewFile();
}

CONFIG_INSTANCE = CommentedFileConfig.ofConcurrent(FreesiaConstants.FileConstants.SECURITY_CONFIG_FILE);

CONFIG_INSTANCE.load();

try {
loadOrDefaultValues();
} catch (Exception e) {
Freesia.LOGGER.error("Failed to load security config!", e);
}

CONFIG_INSTANCE.save();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void onCommandDispatchResult(int traceId, @Nullable String result) {

@Override
public void updateWorkerInfo(UUID workerUUID, String workerName) {
EntryPoint.LOGGER_INST.info("Worker {} (UUID: {}) connected", workerName, workerUUID);
EntryPoint.LOGGER_INST.info("\u001B[36mWorker \u001B[32m{}\u001B[36m (UUID: \u001B[33m{}\u001B[36m) connected\u001B[0m", workerName, workerUUID);

this.workerName = workerName;
this.workerUUID = workerUUID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ public void onChannelMsg(@NotNull PluginMessageEvent event) {

final Consumer<Set<UUID>> targetTask = this.pendingCanSeeTasks.remove(taskId);

try {
targetTask.accept(result);
} catch (Exception e) {
Freesia.LOGGER.error("Can not process tracker callback task !", e);
if (targetTask != null) {
try {
targetTask.accept(result);
} catch (Exception e) {
Freesia.LOGGER.error("Can not process tracker callback task !", e);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.nguyendevs.freesia.velocity;

// The constants are replaced before compilation
public class BuildConstants {

public static final String VERSION = "${version}";
Expand Down
2 changes: 2 additions & 0 deletions Freesia-Waterfall/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dependencies {
implementation("net.kyori:adventure-api:4.14.0")
implementation("net.kyori:adventure-text-minimessage:4.14.0")
implementation("net.kyori:adventure-text-serializer-legacy:4.14.0")
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
}

val targetJavaVersion = 21
Expand Down
Loading
Loading