├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── pl │ └── panszelescik │ └── proxy_protocol_support │ ├── fabric │ └── ProxyProtocolInitializer.java │ └── shared │ ├── ProxyProtocolChannelInitializer.java │ ├── ProxyProtocolHandler.java │ ├── ProxyProtocolSupport.java │ ├── config │ ├── CIDRMatcher.java │ ├── Config.java │ ├── Configuration.java │ └── TCPShieldIntegration.java │ └── mixin │ ├── ChannelInitializerInvoker.java │ ├── ProxyProtocolAddressSetter.java │ └── ProxyProtocolImplementation.java └── resources ├── fabric.mod.json └── proxy_protocol_support.mixins.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: gradle 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Build 9 | 10 | on: 11 | push: 12 | pull_request: 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Set up JDK 21 24 | uses: actions/setup-java@v4 25 | with: 26 | java-version: '21' 27 | distribution: 'temurin' 28 | - name: Validate Gradle wrapper 29 | uses: gradle/actions/wrapper-validation@v4 30 | - name: Setup Gradle 31 | uses: gradle/actions/setup-gradle@v4 32 | - name: Build with Gradle 33 | run: ./gradlew build 34 | - name: Publish Fabric 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: Fabric 38 | path: build/libs/ 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Publish to CurseForge and Modrinth 9 | 10 | on: 11 | release: 12 | types: [published] 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | publish: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Set up JDK 21 24 | uses: actions/setup-java@v4 25 | with: 26 | java-version: '21' 27 | distribution: 'temurin' 28 | - name: Validate Gradle wrapper 29 | uses: gradle/actions/wrapper-validation@v4 30 | - name: Setup Gradle 31 | uses: gradle/actions/setup-gradle@v4 32 | - name: Build with Gradle 33 | run: ./gradlew build 34 | - name: Publish Fabric to CurseForge and Modrinth 35 | uses: Kir-Antipov/mc-publish@v3.3 36 | with: 37 | modrinth-id: mfONdVnp 38 | modrinth-featured: true 39 | modrinth-unfeature-mode: intersection 40 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 41 | 42 | curseforge-id: 623925 43 | curseforge-token: ${{ secrets.CURSEFORGE_API_KEY }} 44 | 45 | files: | 46 | build/libs/!(*-@(dev|dev-shadow|sources|javadoc)).jar 47 | build/libs/*-@(dev|dev-shadow|sources|javadoc).jar 48 | 49 | name: "" 50 | version-type: release 51 | 52 | loaders: | 53 | fabric 54 | quilt 55 | modrinth-game-versions: | 56 | >=1.14 57 | curseforge-game-versions: | 58 | >=1.14 59 | java: | 60 | 22 61 | 21 62 | 20 63 | 19 64 | 18 65 | 17 66 | 16 67 | 15 68 | 14 69 | 13 70 | 12 71 | 11 72 | 10 73 | 9 74 | 8 75 | 76 | retry-attempts: 2 77 | retry-delay: 10000 78 | fail-mode: fail 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2024 PanSzelescik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxy Protocol Support 2 | Proxy Protocol Support is a [Fabric](https://fabricmc.net/) and [Quilt](https://quiltmc.org/) mod which adds support for [Proxy Protocol (HAProxy)](https://www.haproxy.com/blog/haproxy/proxy-protocol/ "Proxy Protocol (HAProxy)") for your Minecraft server. 3 | 4 | For example you can use [TCPShield](https://tcpshield.com/ "TCPShield") or other software ([Nginx](https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_protocol "Nginx")) to forward traffic, and hide your server's IP address. Without Proxy Protocol you can see in console Proxy's IP address. Using and reading Proxy Protocol packet makes showing player's IP address possible. 5 | 6 | Server-Side mod only, on the client does nothing! 7 | 8 | Supports Minecraft versions 1.14-1.21.1 and probably snapshots (untested) 9 | 10 | Looking for Minecraft server? Visit [BedrockHost.pl](https://bedrockhost.pl/) 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.1-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | version = project.mod_version 7 | group = project.maven_group 8 | 9 | repositories { 10 | // Add repositories to retrieve artifacts from in here. 11 | // You should only use this when depending on other mods because 12 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. 13 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html 14 | // for more information about repositories. 15 | } 16 | 17 | dependencies { 18 | // To change the versions see the gradle.properties file 19 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 20 | mappings loom.officialMojangMappings() 21 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 22 | 23 | implementation group: 'io.netty', name: 'netty-codec-haproxy', version: '4.1.114.Final' 24 | include group: 'io.netty', name: 'netty-codec-haproxy', version: '4.1.114.Final' 25 | } 26 | 27 | processResources { 28 | inputs.property "version", project.version 29 | filteringCharset "UTF-8" 30 | 31 | filesMatching("fabric.mod.json") { 32 | expand "version": project.version 33 | } 34 | } 35 | 36 | def targetJavaVersion = 8 37 | tasks.withType(JavaCompile).configureEach { 38 | // ensure that the encoding is set to UTF-8, no matter what the system default is 39 | // this fixes some edge cases with special characters not displaying correctly 40 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 41 | // If Javadoc is generated, this must be specified in that task too. 42 | it.options.encoding = "UTF-8" 43 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { 44 | it.options.release = targetJavaVersion 45 | } 46 | } 47 | 48 | java { 49 | def javaVersion = JavaVersion.toVersion(targetJavaVersion) 50 | if (JavaVersion.current() < javaVersion) { 51 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) 52 | } 53 | archivesBaseName = project.archives_base_name 54 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 55 | // if it is present. 56 | // If you remove this line, sources will not be generated. 57 | withSourcesJar() 58 | } 59 | 60 | jar { 61 | from("LICENSE") { 62 | rename { "${it}_${project.archivesBaseName}" } 63 | } 64 | } 65 | 66 | // configure the maven publication 67 | publishing { 68 | publications { 69 | mavenJava(MavenPublication) { 70 | from components.java 71 | } 72 | } 73 | 74 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 75 | repositories { 76 | // Add repositories to publish to here. 77 | // Notice: This block does NOT have the same function as the block in the top level. 78 | // The repositories here will be used for publishing your artifact, not for 79 | // retrieving dependencies. 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx8G 3 | # Fabric Properties 4 | # check these on https://modmuss50.me/fabric.html 5 | minecraft_version=1.19.3 6 | loader_version=0.16.5 7 | # Mod Properties 8 | mod_version=1.0.5-fabric 9 | maven_group=pl.panszelescik 10 | archives_base_name=proxy_protocol_support 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PanSzelescik/proxy-protocol-support/ad0bc5b0efddfcaa75460763f1b8007337a43fba/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PanSzelescik/proxy-protocol-support/ad0bc5b0efddfcaa75460763f1b8007337a43fba/gradlew -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/fabric/ProxyProtocolInitializer.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.fabric; 2 | 3 | import net.fabricmc.api.DedicatedServerModInitializer; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import pl.panszelescik.proxy_protocol_support.shared.config.Config; 8 | import pl.panszelescik.proxy_protocol_support.shared.config.Configuration; 9 | import pl.panszelescik.proxy_protocol_support.shared.ProxyProtocolSupport; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * Fabric's main file 15 | * 16 | * @author PanSzelescik 17 | */ 18 | @Environment(EnvType.SERVER) 19 | public class ProxyProtocolInitializer implements DedicatedServerModInitializer { 20 | 21 | @Override 22 | public void onInitializeServer() { 23 | try { 24 | Config config = Configuration.loadConfig(FabricLoader.getInstance().getConfigDir().toFile()); 25 | ProxyProtocolSupport.initialize(config); 26 | } catch (IOException e) { 27 | ProxyProtocolSupport.errorLogger.accept("Error loading config file:"); 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/ProxyProtocolChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; 6 | import pl.panszelescik.proxy_protocol_support.shared.mixin.ChannelInitializerInvoker; 7 | 8 | /** 9 | * Initializes HAProxyMessageDecoder and ProxyProtocolHandler 10 | * 11 | * @author PanSzelescik 12 | * @see io.netty.handler.codec.haproxy.HAProxyMessageDecoder 13 | * @see pl.panszelescik.proxy_protocol_support.shared.ProxyProtocolHandler 14 | */ 15 | public class ProxyProtocolChannelInitializer extends ChannelInitializer { 16 | 17 | private final ChannelInitializerInvoker channelInitializer; 18 | 19 | public ProxyProtocolChannelInitializer(ChannelInitializerInvoker invoker) { 20 | this.channelInitializer = invoker; 21 | } 22 | 23 | @Override 24 | protected void initChannel(Channel channel) throws Exception { 25 | this.channelInitializer.invokeInitChannel(channel); 26 | 27 | if (!ProxyProtocolSupport.enableProxyProtocol) { 28 | return; 29 | } 30 | 31 | channel.pipeline() 32 | .addAfter("timeout", "haproxy-decoder", new HAProxyMessageDecoder()) 33 | .addAfter("haproxy-decoder", "haproxy-handler", new ProxyProtocolHandler()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/ProxyProtocolHandler.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.codec.haproxy.HAProxyCommand; 6 | import io.netty.handler.codec.haproxy.HAProxyMessage; 7 | import net.minecraft.network.Connection; 8 | import pl.panszelescik.proxy_protocol_support.shared.config.CIDRMatcher; 9 | import pl.panszelescik.proxy_protocol_support.shared.mixin.ProxyProtocolAddressSetter; 10 | 11 | import java.net.InetSocketAddress; 12 | import java.net.SocketAddress; 13 | 14 | /** 15 | * Reads HAProxyMessage to set valid Player IP 16 | * 17 | * @author PanSzelescik 18 | * @see io.netty.handler.codec.haproxy.HAProxyMessage 19 | */ 20 | public class ProxyProtocolHandler extends ChannelInboundHandlerAdapter { 21 | 22 | @Override 23 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 24 | if (msg instanceof HAProxyMessage) { 25 | HAProxyMessage message = ((HAProxyMessage) msg); 26 | if (message.command() == HAProxyCommand.PROXY) { 27 | final String realAddress = message.sourceAddress(); 28 | final int realPort = message.sourcePort(); 29 | 30 | final InetSocketAddress socketAddr = new InetSocketAddress(realAddress, realPort); 31 | 32 | Connection connection = ((Connection) ctx.channel().pipeline().get("packet_handler")); 33 | SocketAddress proxyAddress = connection.getRemoteAddress(); 34 | 35 | if (!ProxyProtocolSupport.whitelistedIPs.isEmpty()) { 36 | if (proxyAddress instanceof InetSocketAddress) { 37 | InetSocketAddress proxySocketAddress = ((InetSocketAddress) proxyAddress); 38 | boolean isWhitelistedIP = false; 39 | 40 | for (CIDRMatcher matcher : ProxyProtocolSupport.whitelistedIPs) { 41 | if (matcher.matches(proxySocketAddress.getAddress())) { 42 | isWhitelistedIP = true; 43 | break; 44 | } 45 | } 46 | 47 | if (!isWhitelistedIP) { 48 | if (ctx.channel().isOpen()) { 49 | ctx.disconnect(); 50 | ProxyProtocolSupport.warnLogger.accept("Blocked proxy IP: " + proxySocketAddress + " when tried to connect!"); 51 | } 52 | return; 53 | } 54 | } else { 55 | ProxyProtocolSupport.warnLogger.accept("**********************************************************************"); 56 | ProxyProtocolSupport.warnLogger.accept("* Detected other SocketAddress than InetSocketAddress! *"); 57 | ProxyProtocolSupport.warnLogger.accept("* Please report it with logs to mod author to provide compatibility! *"); 58 | ProxyProtocolSupport.warnLogger.accept("* https://github.com/PanSzelescik/proxy-protocol-support/issues *"); 59 | ProxyProtocolSupport.warnLogger.accept("**********************************************************************"); 60 | ProxyProtocolSupport.warnLogger.accept(proxyAddress.getClass().toString()); 61 | ProxyProtocolSupport.warnLogger.accept(proxyAddress.toString()); 62 | } 63 | } 64 | 65 | ((ProxyProtocolAddressSetter) connection).setAddress(socketAddr); 66 | } 67 | } else { 68 | super.channelRead(ctx, msg); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/ProxyProtocolSupport.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared; 2 | 3 | import pl.panszelescik.proxy_protocol_support.shared.config.CIDRMatcher; 4 | import pl.panszelescik.proxy_protocol_support.shared.config.Config; 5 | import pl.panszelescik.proxy_protocol_support.shared.config.TCPShieldIntegration; 6 | 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.function.Consumer; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | /** 15 | * Simple class for getting settings 16 | * 17 | * @author PanSzelescik 18 | */ 19 | public class ProxyProtocolSupport { 20 | 21 | public static final String MODID = "proxy_protocol_support"; 22 | 23 | public static Consumer infoLogger; 24 | public static Consumer warnLogger; 25 | public static Consumer errorLogger; 26 | 27 | public static boolean enableProxyProtocol = false; 28 | public static Collection whitelistedIPs = new ArrayList<>(); 29 | 30 | public static void initialize(Config config) throws IOException { 31 | if (!config.enableProxyProtocol) { 32 | infoLogger.accept("Proxy Protocol disabled!"); 33 | return; 34 | } 35 | 36 | infoLogger.accept("Proxy Protocol enabled!"); 37 | 38 | enableProxyProtocol = config.enableProxyProtocol; 39 | whitelistedIPs = config.whitelistedIPs 40 | .stream() 41 | .map(CIDRMatcher::new) 42 | .collect(Collectors.toSet()); 43 | 44 | if (config.whitelistTCPShieldServers) { 45 | infoLogger.accept("TCPShield integration enabled!"); 46 | whitelistedIPs = Stream 47 | .concat(whitelistedIPs.stream(), TCPShieldIntegration.getWhitelistedIPs().stream()) 48 | .collect(Collectors.toSet()); 49 | } 50 | 51 | infoLogger.accept("Using " + whitelistedIPs.size() + " whitelisted IPs: " + whitelistedIPs); 52 | } 53 | 54 | static { 55 | try { 56 | org.slf4j.Logger slf4j = org.slf4j.LoggerFactory.getLogger(MODID); 57 | infoLogger = slf4j::info; 58 | warnLogger = slf4j::warn; 59 | errorLogger = slf4j::error; 60 | } catch (Throwable ignored) { 61 | try { 62 | org.apache.logging.log4j.Logger log4j = org.apache.logging.log4j.LogManager.getLogger(MODID); 63 | infoLogger = log4j::info; 64 | warnLogger = log4j::warn; 65 | errorLogger = log4j::error; 66 | } catch (Throwable ignored2) { 67 | infoLogger = System.out::println; 68 | warnLogger = System.out::println; 69 | errorLogger = System.out::println; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/config/CIDRMatcher.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.config; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | 6 | /** 7 | * Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE 8 | * 9 | * https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java 10 | */ 11 | public class CIDRMatcher { 12 | private final int maskBits; 13 | private final int maskBytes; 14 | private final boolean simpleCIDR; 15 | private final InetAddress cidrAddress; 16 | 17 | public CIDRMatcher(String ipAddress) { 18 | String[] split = ipAddress.split("/", 2); 19 | 20 | String parsedIPAddress; 21 | if (split.length == 2) { 22 | parsedIPAddress = split[0]; 23 | 24 | this.maskBits = Integer.parseInt(split[1]); 25 | this.simpleCIDR = maskBits == 32; 26 | } else { 27 | parsedIPAddress = ipAddress; 28 | 29 | this.maskBits = -1; 30 | this.simpleCIDR = true; 31 | } 32 | 33 | this.maskBytes = simpleCIDR ? -1 : maskBits / 8; 34 | 35 | try { 36 | cidrAddress = InetAddress.getByName(parsedIPAddress); 37 | } catch (UnknownHostException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | public boolean matches(InetAddress inetAddress) { 43 | // check if IP is IPv4 or IPv6 44 | if (cidrAddress.getClass() != inetAddress.getClass()) { 45 | return false; 46 | } 47 | 48 | // check for equality if it's a simple CIDR 49 | if (simpleCIDR) { 50 | return inetAddress.equals(cidrAddress); 51 | } 52 | 53 | byte[] inetAddressBytes = inetAddress.getAddress(); 54 | byte[] requiredAddressBytes = cidrAddress.getAddress(); 55 | 56 | byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07)); 57 | 58 | for (int i = 0; i < maskBytes; i++) { 59 | if (inetAddressBytes[i] != requiredAddressBytes[i]) { 60 | return false; 61 | } 62 | } 63 | 64 | if (finalByte != 0) { 65 | return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte); 66 | } 67 | 68 | return true; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return simpleCIDR ? cidrAddress.toString().substring(1) : cidrAddress.toString().substring(1) + "/" + maskBits; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/config/Config.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.config; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Class which represents configuration file 10 | * 11 | * @author PanSzelescik 12 | * @see Configuration 13 | */ 14 | public class Config { 15 | 16 | @SerializedName("enable-proxy-protocol") 17 | public boolean enableProxyProtocol = true; 18 | 19 | @SerializedName("proxy-protocol-whitelisted-ips") 20 | public List whitelistedIPs = new ArrayList<>(); 21 | 22 | @SerializedName("whitelistTCPShieldServers") 23 | public boolean whitelistTCPShieldServers = false; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/config/Configuration.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.config; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import org.apache.commons.io.FileUtils; 6 | import pl.panszelescik.proxy_protocol_support.shared.ProxyProtocolSupport; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | /** 13 | * Loads and saves Config 14 | * 15 | * @author PanSzelescik 16 | * @see Config 17 | */ 18 | public class Configuration { 19 | 20 | private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); 21 | 22 | public static Config loadConfig(File configDir) throws IOException { 23 | final File file = new File(configDir, ProxyProtocolSupport.MODID + ".json"); 24 | 25 | if (file.exists()) { 26 | return saveConfig(file, loadConfigFile(file)); 27 | } 28 | 29 | return saveDefaultConfig(file); 30 | } 31 | 32 | private static Config loadConfigFile(File configFile) throws IOException { 33 | final String string = FileUtils.readFileToString(configFile, StandardCharsets.UTF_8); 34 | 35 | return GSON.fromJson(string, Config.class); 36 | } 37 | 38 | private static Config saveDefaultConfig(File configFile) throws IOException { 39 | return saveConfig(configFile, new Config()); 40 | } 41 | 42 | private static Config saveConfig(File configFile, Config config) throws IOException { 43 | final String string = GSON.toJson(config); 44 | 45 | FileUtils.writeStringToFile(configFile, string, StandardCharsets.UTF_8); 46 | 47 | return config; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/config/TCPShieldIntegration.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.config; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.URI; 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * Adds TCPShield's servers to whitelisted IPs 11 | * 12 | * @author PanSzelescik 13 | */ 14 | public class TCPShieldIntegration { 15 | 16 | private static final URI IPV4 = URI.create("https://tcpshield.com/v4/"); 17 | private static final URI IPV4_CF = URI.create("https://tcpshield.com/v4-cf/"); 18 | 19 | public static ArrayList getWhitelistedIPs() throws IOException { 20 | ArrayList matchers = new ArrayList<>(); 21 | 22 | readFromUrl(matchers, IPV4); 23 | readFromUrl(matchers, IPV4_CF); 24 | 25 | return matchers; 26 | } 27 | 28 | private static void readFromUrl(ArrayList matchers, URI uri) throws IOException { 29 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(uri.toURL().openStream()))) { 30 | while (reader.ready()) { 31 | String line = reader.readLine().trim(); 32 | if (!line.isEmpty()) { 33 | matchers.add(new CIDRMatcher(line)); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/mixin/ChannelInitializerInvoker.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.mixin; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelInitializer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | /** 9 | * Adds Invoker for initChannel 10 | * 11 | * @author PanSzelescik 12 | * @see io.netty.channel.ChannelInitializer#initChannel(Channel) 13 | */ 14 | @Mixin(ChannelInitializer.class) 15 | public interface ChannelInitializerInvoker { 16 | 17 | @Invoker(value = "initChannel", remap = false) 18 | void invokeInitChannel(Channel ch) throws Exception; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/mixin/ProxyProtocolAddressSetter.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.mixin; 2 | 3 | import net.minecraft.network.Connection; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | import java.net.SocketAddress; 8 | 9 | /** 10 | * Adds Accessor for address 11 | * 12 | * @author PanSzelescik 13 | * @see net.minecraft.network.Connection#address 14 | */ 15 | @Mixin(Connection.class) 16 | public interface ProxyProtocolAddressSetter { 17 | 18 | @Accessor 19 | void setAddress(SocketAddress address); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/pl/panszelescik/proxy_protocol_support/shared/mixin/ProxyProtocolImplementation.java: -------------------------------------------------------------------------------- 1 | package pl.panszelescik.proxy_protocol_support.shared.mixin; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelHandler; 5 | import net.minecraft.server.network.ServerConnectionListener; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | import pl.panszelescik.proxy_protocol_support.shared.ProxyProtocolChannelInitializer; 10 | 11 | /** 12 | * Replaces anonymous ChannelInitializer with ProxyProtocolChannelInitializer 13 | * 14 | * @author PanSzelescik 15 | * @see pl.panszelescik.proxy_protocol_support.shared.ProxyProtocolChannelInitializer 16 | */ 17 | @Mixin(ServerConnectionListener.class) 18 | public class ProxyProtocolImplementation { 19 | 20 | // TODO: Mixin into anonymous class? 21 | @Redirect(method = "startTcpServerListener", at = @At(value = "INVOKE", target = "Lio/netty/bootstrap/ServerBootstrap;childHandler(Lio/netty/channel/ChannelHandler;)Lio/netty/bootstrap/ServerBootstrap;", remap = false)) 22 | private ServerBootstrap addProxyProtocolSupport(ServerBootstrap bootstrap, ChannelHandler childHandler) { 23 | return bootstrap.childHandler(new ProxyProtocolChannelInitializer(((ChannelInitializerInvoker) childHandler))); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "proxy_protocol_support", 4 | "version": "${version}", 5 | "name": "Proxy Protocol Support", 6 | "description": "Proxy Protocol support for Fabric and Quilt servers", 7 | "authors": [ 8 | "PanSzelescik" 9 | ], 10 | "contact": { 11 | "website": "https://www.curseforge.com/minecraft/mc-mods/proxy-protocol-support", 12 | "homepage": "https://modrinth.com/mod/proxy-protocol-support", 13 | "repo": "https://github.com/PanSzelescik/proxy-protocol-support", 14 | "issues": "https://github.com/PanSzelescik/proxy-protocol-support/issues" 15 | }, 16 | "license": "MIT", 17 | "environment": "server", 18 | "entrypoints": { 19 | "server": [ 20 | "pl.panszelescik.proxy_protocol_support.fabric.ProxyProtocolInitializer" 21 | ] 22 | }, 23 | "mixins": [ 24 | "proxy_protocol_support.mixins.json" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/proxy_protocol_support.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "pl.panszelescik.proxy_protocol_support.shared.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "server": [ 7 | "ChannelInitializerInvoker", 8 | "ProxyProtocolAddressSetter", 9 | "ProxyProtocolImplementation" 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } 15 | --------------------------------------------------------------------------------