├── .github └── workflows │ ├── deploy-release.yml │ ├── deploy-snapshot.yml │ └── deploy.yml ├── .gitignore ├── .idea └── copyright │ ├── APL.xml │ └── profiles_settings.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── codec-query ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── cloudburstmc │ └── netty │ └── handler │ └── codec │ └── query │ ├── QueryEventListener.java │ ├── QueryNetworkListener.java │ ├── QueryPacket.java │ ├── QueryUtil.java │ ├── codec │ └── QueryPacketCodec.java │ ├── enveloped │ └── DirectAddressedQueryPacket.java │ ├── handler │ └── QueryPacketHandler.java │ └── packet │ ├── HandshakePacket.java │ └── StatisticsPacket.java ├── codec-rcon ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── cloudburstmc │ └── netty │ └── handler │ └── codec │ └── rcon │ ├── RconCodec.java │ ├── RconEventListener.java │ ├── RconMessage.java │ ├── RconNetworkListener.java │ └── handler │ └── RconHandler.java ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── transport-raknet ├── README.md ├── build.gradle.kts └── src ├── main └── java │ └── org │ └── cloudburstmc │ └── netty │ ├── channel │ ├── proxy │ │ ├── ProxyChannel.java │ │ └── package-info.java │ └── raknet │ │ ├── RakChannel.java │ │ ├── RakChannelFactory.java │ │ ├── RakChannelPipeline.java │ │ ├── RakChildChannel.java │ │ ├── RakClientChannel.java │ │ ├── RakConstants.java │ │ ├── RakDisconnectReason.java │ │ ├── RakEvent.java │ │ ├── RakOfflineState.java │ │ ├── RakPing.java │ │ ├── RakPong.java │ │ ├── RakPriority.java │ │ ├── RakReliability.java │ │ ├── RakServerChannel.java │ │ ├── RakSlidingWindow.java │ │ ├── RakState.java │ │ ├── config │ │ ├── DefaultRakClientConfig.java │ │ ├── DefaultRakServerConfig.java │ │ ├── DefaultRakSessionConfig.java │ │ ├── RakChannelConfig.java │ │ ├── RakChannelMetrics.java │ │ ├── RakChannelOption.java │ │ ├── RakServerChannelConfig.java │ │ ├── RakServerMetrics.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── packet │ │ ├── EncapsulatedPacket.java │ │ ├── RakDatagramPacket.java │ │ ├── RakMessage.java │ │ └── package-info.java │ ├── handler │ └── codec │ │ └── raknet │ │ ├── AdvancedChannelInboundHandler.java │ │ ├── ProxyInboundRouter.java │ │ ├── ProxyOutboundRouter.java │ │ ├── client │ │ ├── RakClientOfflineHandler.java │ │ ├── RakClientOnlineInitialHandler.java │ │ ├── RakClientProxyRouteHandler.java │ │ ├── RakClientRouteHandler.java │ │ └── package-info.java │ │ ├── common │ │ ├── ConnectedPingHandler.java │ │ ├── ConnectedPongHandler.java │ │ ├── DisconnectNotificationHandler.java │ │ ├── EncapsulatedToMessageHandler.java │ │ ├── RakAcknowledgeHandler.java │ │ ├── RakDatagramCodec.java │ │ ├── RakSessionCodec.java │ │ ├── RakUnhandledMessagesQueue.java │ │ ├── UnconnectedPingEncoder.java │ │ ├── UnconnectedPongDecoder.java │ │ ├── UnconnectedPongEncoder.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── server │ │ ├── RakChildDatagramHandler.java │ │ ├── RakServerOfflineHandler.java │ │ ├── RakServerOnlineInitialHandler.java │ │ ├── RakServerRateLimiter.java │ │ ├── RakServerRouteHandler.java │ │ ├── RakServerTailHandler.java │ │ └── package-info.java │ └── util │ ├── BitQueue.java │ ├── FastBinaryMinHeap.java │ ├── IntRange.java │ ├── IpDontFragmentProvider.java │ ├── RakUtils.java │ ├── RoundRobinArray.java │ ├── RoundRobinIterator.java │ ├── SecureAlgorithmProvider.java │ ├── SplitPacketHelper.java │ └── package-info.java └── test └── java └── org └── cloudburstmc └── netty ├── BitQueueTests.java └── RakTests.java /.github/workflows/deploy-release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | deploy: 9 | uses: CloudburstMC/Network/.github/workflows/deploy.yml@develop 10 | with: 11 | deploy-url: "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 12 | secrets: 13 | DEPLOY_USERNAME: ${{ secrets.OSSRH_USERNAME }} 14 | DEPLOY_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 15 | PGP_SECRET: ${{ secrets.MAVEN_CENTRAL_SECRET }} 16 | PGP_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_PASSPHRASE }} 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Snapshot 2 | on: 3 | push: 4 | branches: 5 | - 'develop' 6 | 7 | jobs: 8 | deploy: 9 | uses: CloudburstMC/Network/.github/workflows/deploy.yml@develop 10 | with: 11 | deploy-url: "https://repo.opencollab.dev/maven-snapshots/" 12 | secrets: 13 | DEPLOY_USERNAME: ${{ secrets.OPENCOLLAB_USERNAME }} 14 | DEPLOY_PASSWORD: ${{ secrets.OPENCOLLAB_PASSWORD }} 15 | PGP_SECRET: ${{ secrets.MAVEN_CENTRAL_SECRET }} 16 | PGP_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_PASSPHRASE }} 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | deploy-url: 5 | description: 'The maven repository to deploy to' 6 | required: true 7 | type: string 8 | secrets: 9 | DEPLOY_USERNAME: 10 | required: true 11 | DEPLOY_PASSWORD: 12 | required: true 13 | PGP_SECRET: 14 | required: true 15 | PGP_PASSPHRASE: 16 | required: true 17 | 18 | jobs: 19 | publish: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-java@v3 24 | with: 25 | distribution: 'temurin' 26 | java-version: '8' 27 | - name: Validate Gradle Wrapper 28 | uses: gradle/wrapper-validation-action@v1 29 | - name: Build 30 | uses: gradle/gradle-build-action@v2 31 | with: 32 | gradle-version: wrapper 33 | arguments: "publish" 34 | env: 35 | MAVEN_DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }} 36 | MAVEN_DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }} 37 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 38 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 39 | MAVEN_DEPLOY_URL: ${{ inputs.deploy-url }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse,netbeans,intellij+all 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,eclipse,netbeans,intellij+all 3 | 4 | ### Eclipse ### 5 | .metadata 6 | bin/ 7 | tmp/ 8 | *.tmp 9 | *.bak 10 | *.swp 11 | *~.nib 12 | local.properties 13 | .settings/ 14 | .loadpath 15 | .recommenders 16 | 17 | # External tool builders 18 | .externalToolBuilders/ 19 | 20 | # Locally stored "Eclipse launch configurations" 21 | *.launch 22 | 23 | # PyDev specific (Python IDE for Eclipse) 24 | *.pydevproject 25 | 26 | # CDT-specific (C/C++ Development Tooling) 27 | .cproject 28 | 29 | # CDT- autotools 30 | .autotools 31 | 32 | # Java annotation processor (APT) 33 | .factorypath 34 | 35 | # PDT-specific (PHP Development Tools) 36 | .buildpath 37 | 38 | # sbteclipse plugin 39 | .target 40 | 41 | # Tern plugin 42 | .tern-project 43 | 44 | # TeXlipse plugin 45 | .texlipse 46 | 47 | # STS (Spring Tool Suite) 48 | .springBeans 49 | 50 | # Code Recommenders 51 | .recommenders/ 52 | 53 | # Annotation Processing 54 | .apt_generated/ 55 | .apt_generated_test/ 56 | 57 | # Scala IDE specific (Scala & Java development for Eclipse) 58 | .cache-main 59 | .scala_dependencies 60 | .worksheet 61 | 62 | # Uncomment this line if you wish to ignore the project description file. 63 | # Typically, this file would be tracked if it contains build/dependency configurations: 64 | #.project 65 | 66 | ### Eclipse Patch ### 67 | # Spring Boot Tooling 68 | .sts4-cache/ 69 | 70 | ### Intellij+all ### 71 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 72 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 73 | 74 | # User-specific stuff 75 | .idea/**/workspace.xml 76 | .idea/**/tasks.xml 77 | .idea/**/usage.statistics.xml 78 | .idea/**/dictionaries 79 | .idea/**/shelf 80 | 81 | # AWS User-specific 82 | .idea/**/aws.xml 83 | 84 | # Generated files 85 | .idea/**/contentModel.xml 86 | 87 | # Sensitive or high-churn files 88 | .idea/**/dataSources/ 89 | .idea/**/dataSources.ids 90 | .idea/**/dataSources.local.xml 91 | .idea/**/sqlDataSources.xml 92 | .idea/**/dynamic.xml 93 | .idea/**/uiDesigner.xml 94 | .idea/**/dbnavigator.xml 95 | 96 | # Gradle 97 | .idea/**/gradle.xml 98 | .idea/**/libraries 99 | 100 | # Gradle and Maven with auto-import 101 | # When using Gradle or Maven with auto-import, you should exclude module files, 102 | # since they will be recreated, and may cause churn. Uncomment if using 103 | # auto-import. 104 | .idea/artifacts 105 | .idea/compiler.xml 106 | .idea/jarRepositories.xml 107 | .idea/modules.xml 108 | .idea/*.iml 109 | .idea/modules 110 | *.iml 111 | *.ipr 112 | .idea/codeStyles/* 113 | 114 | # CMake 115 | cmake-build-*/ 116 | 117 | # Mongo Explorer plugin 118 | .idea/**/mongoSettings.xml 119 | 120 | # File-based project format 121 | *.iws 122 | 123 | # IntelliJ 124 | out/ 125 | 126 | # mpeltonen/sbt-idea plugin 127 | .idea_modules/ 128 | 129 | # JIRA plugin 130 | atlassian-ide-plugin.xml 131 | 132 | # Cursive Clojure plugin 133 | .idea/replstate.xml 134 | 135 | # SonarLint plugin 136 | .idea/sonarlint/ 137 | 138 | # Crashlytics plugin (for Android Studio and IntelliJ) 139 | com_crashlytics_export_strings.xml 140 | crashlytics.properties 141 | crashlytics-build.properties 142 | fabric.properties 143 | 144 | # Editor-based Rest Client 145 | .idea/httpRequests 146 | 147 | # Android studio 3.1+ serialized cache file 148 | .idea/caches/build_file_checksums.ser 149 | 150 | ### Intellij+all Patch ### 151 | # Ignore everything but code style settings and run configurations 152 | # that are supposed to be shared within teams. 153 | 154 | .idea/* 155 | 156 | !.idea/codeStyles 157 | !.idea/runConfigurations 158 | 159 | ### Java ### 160 | # Compiled class file 161 | *.class 162 | 163 | # Log file 164 | *.log 165 | 166 | # BlueJ files 167 | *.ctxt 168 | 169 | # Mobile Tools for Java (J2ME) 170 | .mtj.tmp/ 171 | 172 | # Package Files # 173 | *.jar 174 | *.war 175 | *.nar 176 | *.ear 177 | *.zip 178 | *.tar.gz 179 | *.rar 180 | 181 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 182 | hs_err_pid* 183 | replay_pid* 184 | 185 | ### NetBeans ### 186 | **/nbproject/private/ 187 | **/nbproject/Makefile-*.mk 188 | **/nbproject/Package-*.bash 189 | build/ 190 | nbbuild/ 191 | dist/ 192 | nbdist/ 193 | .nb-gradle/ 194 | 195 | ### Gradle ### 196 | .gradle 197 | **/build/ 198 | !src/**/build/ 199 | 200 | # Ignore Gradle GUI config 201 | gradle-app.setting 202 | 203 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 204 | !gradle-wrapper.jar 205 | 206 | # Avoid ignore Gradle wrappper properties 207 | !gradle-wrapper.properties 208 | 209 | # Cache of project 210 | .gradletasknamecache 211 | 212 | # Eclipse Gradle plugin generated files 213 | # Eclipse Core 214 | .project 215 | # JDT-specific (Eclipse Java Development Tools) 216 | .classpath 217 | 218 | ### Gradle Patch ### 219 | # Java heap dump 220 | *.hprof 221 | 222 | # End of https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse,netbeans,intellij+all 223 | -------------------------------------------------------------------------------- /.idea/copyright/APL.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network 2 | 3 | ### Introduction 4 | 5 | Network components used within Cloudburst projects. 6 | 7 | ### Components 8 | 9 | - [`netty-transport-raknet`](transport-raknet/README.md) - A RakNet implementation based on Netty patterns 10 | 11 | ### Maven 12 | 13 | ##### Repository: 14 | 15 | For releases, use Maven Central. 16 | Snapshots can be found in the repository below. 17 | 18 |
19 | Gradle (Kotlin DSL) 20 | 21 | ```kotlin 22 | repositories { 23 | maven("https://repo.opencollab.dev/maven-snapshots/") 24 | } 25 | ``` 26 | 27 |
28 |
29 | Gradle 30 | 31 | ```groovy 32 | repositories { 33 | maven { 34 | url 'https://repo.opencollab.dev/maven-snapshots/' 35 | } 36 | } 37 | ``` 38 | 39 |
40 |
41 | Maven 42 | 43 | ```xml 44 | 45 | 46 | 47 | opencollab-snapshots 48 | https://repo.opencollab.dev/maven-snapshots/ 49 | 50 | 51 | ``` 52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | subprojects { 18 | apply(plugin = "java-library") 19 | apply(plugin = "maven-publish") 20 | apply(plugin = "signing") 21 | 22 | group = "org.cloudburstmc.netty" 23 | version = rootProject.property("version") as String 24 | 25 | repositories { 26 | mavenLocal() 27 | mavenCentral() 28 | } 29 | 30 | configure { 31 | toolchain { 32 | languageVersion.set(JavaLanguageVersion.of(8)) 33 | } 34 | withJavadocJar() 35 | withSourcesJar() 36 | } 37 | 38 | configure { 39 | repositories { 40 | maven { 41 | name = "maven-deploy" 42 | url = uri( 43 | System.getenv("MAVEN_DEPLOY_URL") 44 | ?: "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 45 | ) 46 | credentials { 47 | username = System.getenv("MAVEN_DEPLOY_USERNAME") ?: "username" 48 | password = System.getenv("MAVEN_DEPLOY_PASSWORD") ?: "password" 49 | } 50 | } 51 | } 52 | publications { 53 | create("maven") { 54 | artifactId = "netty-${project.name}" 55 | 56 | from(components["java"]) 57 | 58 | pom { 59 | description.set(project.description) 60 | url.set("https://github.com/CloudburstMC/Network") 61 | inceptionYear.set("2018") 62 | licenses { 63 | license { 64 | name.set("The Apache License, Version 2.0") 65 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 66 | } 67 | } 68 | developers { 69 | developer { 70 | name.set("CloudburstMC Team") 71 | organization.set("CloudburstMC") 72 | organizationUrl.set("https://github.com/CloudburstMC") 73 | } 74 | } 75 | scm { 76 | connection.set("scm:git:git://github.com/CloudburstMC/Network.git") 77 | developerConnection.set("scm:git:ssh://github.com:CloudburstMC/my-library.git") 78 | url.set("https://github.com/CloudburstMC/Network") 79 | } 80 | ciManagement { 81 | system.set("GitHub Actions") 82 | url.set("https://github.com/CloudburstMC/Network/actions") 83 | } 84 | issueManagement { 85 | system.set("GitHub Issues") 86 | url.set("https://github.com/CloudburstMC/Network/issues") 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | configure { 94 | if (System.getenv("PGP_SECRET") != null && System.getenv("PGP_PASSPHRASE") != null) { 95 | useInMemoryPgpKeys(System.getenv("PGP_SECRET"), System.getenv("PGP_PASSPHRASE")) 96 | sign(project.extensions.getByType(PublishingExtension::class).publications["maven"]) 97 | } 98 | } 99 | 100 | tasks { 101 | named("compileJava") { 102 | options.encoding = "UTF-8" 103 | } 104 | named("test") { 105 | minHeapSize = "512m" 106 | maxHeapSize = "1024m" 107 | jvmArgs = listOf("-XX:MaxMetaspaceSize=512m") 108 | useJUnitPlatform() 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /codec-query/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | cloudburstmc-netty-parent 7 | org.cloudburstmc.netty 8 | 1.0.0.Final-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-codec-query 13 | Netty/Codec/Query 14 | 15 | 16 | 17 | io.netty 18 | netty-common 19 | ${netty.version} 20 | 21 | 22 | io.netty 23 | netty-buffer 24 | ${netty.version} 25 | 26 | 27 | io.netty 28 | netty-codec 29 | ${netty.version} 30 | 31 | 32 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/QueryEventListener.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import lombok.Value; 6 | import lombok.experimental.NonFinal; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.StringJoiner; 12 | 13 | public interface QueryEventListener { 14 | 15 | Data onQuery(InetSocketAddress address); 16 | 17 | @Value 18 | class Data { 19 | private final String hostname; 20 | private final String gametype; 21 | private final String map; 22 | private final int playerCount; 23 | private final int maxPlayerCount; 24 | private final int hostport; 25 | private final String hostip; 26 | private final String gameId; 27 | private final String version; 28 | private final String softwareVersion; 29 | private final boolean whitelisted; 30 | private final String[] plugins; 31 | private final String[] players; 32 | @NonFinal 33 | private transient ByteBuf longStats; 34 | @NonFinal 35 | private transient ByteBuf shortStats; 36 | 37 | public ByteBuf getLongStats() { 38 | if (longStats != null) { 39 | return longStats; 40 | } 41 | 42 | longStats = ByteBufAllocator.DEFAULT.buffer(); 43 | 44 | longStats.writeBytes(QueryUtil.LONG_RESPONSE_PADDING_TOP); 45 | 46 | StringJoiner plugins = new StringJoiner(";"); 47 | if (this.plugins != null) { 48 | for (String plugin : this.plugins) { 49 | plugins.add(plugin); 50 | } 51 | } 52 | 53 | Map kvs = new HashMap<>(); 54 | kvs.put("hostname", hostname); 55 | kvs.put("gametype", gametype); 56 | kvs.put("map", map); 57 | kvs.put("numplayers", Integer.toString(playerCount)); 58 | kvs.put("maxplayers", Integer.toString(maxPlayerCount)); 59 | kvs.put("hostport", Integer.toString(hostport)); 60 | kvs.put("hostip", hostip); 61 | kvs.put("game_id", gameId); 62 | kvs.put("version", version); 63 | kvs.put("plugins", softwareVersion + plugins.toString()); 64 | kvs.put("whitelist", whitelisted ? "on" : "off"); 65 | 66 | kvs.forEach((key, value) -> { 67 | QueryUtil.writeNullTerminatedString(longStats, key); 68 | QueryUtil.writeNullTerminatedString(longStats, value); 69 | }); 70 | 71 | longStats.writeByte(0); 72 | longStats.writeBytes(QueryUtil.LONG_RESPONSE_PADDING_BOTTOM); 73 | 74 | if (players != null) { 75 | for (String player : players) { 76 | QueryUtil.writeNullTerminatedString(longStats, player); 77 | } 78 | } 79 | longStats.writeByte(0); 80 | 81 | return longStats; 82 | } 83 | 84 | public ByteBuf getShortStats() { 85 | if (shortStats != null) { 86 | return shortStats; 87 | } 88 | 89 | shortStats = ByteBufAllocator.DEFAULT.buffer(); 90 | 91 | QueryUtil.writeNullTerminatedString(shortStats, hostname); 92 | QueryUtil.writeNullTerminatedString(shortStats, gametype); 93 | QueryUtil.writeNullTerminatedString(shortStats, map); 94 | QueryUtil.writeNullTerminatedString(shortStats, Integer.toString(playerCount)); 95 | QueryUtil.writeNullTerminatedString(shortStats, Integer.toString(maxPlayerCount)); 96 | shortStats.writeShortLE(hostport); 97 | QueryUtil.writeNullTerminatedString(shortStats, hostip); 98 | 99 | return shortStats; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/QueryNetworkListener.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query; 2 | 3 | import com.nukkitx.network.NetworkListener; 4 | import com.nukkitx.network.util.Bootstraps; 5 | import com.nukkitx.network.util.EventLoops; 6 | import com.nukkitx.network.util.Preconditions; 7 | import io.netty.bootstrap.Bootstrap; 8 | import io.netty.buffer.ByteBufAllocator; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.socket.DatagramChannel; 13 | import org.cloudburstmc.netty.handler.codec.query.codec.QueryPacketCodec; 14 | import org.cloudburstmc.netty.handler.codec.query.handler.QueryPacketHandler; 15 | 16 | import java.net.InetSocketAddress; 17 | 18 | public class QueryNetworkListener extends ChannelInitializer implements NetworkListener { 19 | private final InetSocketAddress address; 20 | private final QueryEventListener eventListener; 21 | private final Bootstrap bootstrap; 22 | private DatagramChannel channel; 23 | 24 | public QueryNetworkListener(InetSocketAddress address, QueryEventListener eventListener) { 25 | this.address = address; 26 | this.eventListener = eventListener; 27 | 28 | bootstrap = new Bootstrap().option(ChannelOption.ALLOCATOR, ByteBufAllocator.DEFAULT).handler(this); 29 | 30 | Bootstraps.setupBootstrap(bootstrap, true); 31 | this.bootstrap.group(EventLoops.commonGroup()); 32 | } 33 | 34 | @Override 35 | public boolean bind() { 36 | Preconditions.checkState(channel == null, "Channel already initialized"); 37 | 38 | ChannelFuture future = bootstrap.bind(address).awaitUninterruptibly(); 39 | 40 | return future.isSuccess(); 41 | } 42 | 43 | @Override 44 | public void close() { 45 | if (channel != null) { 46 | channel.close().syncUninterruptibly(); 47 | } 48 | } 49 | 50 | @Override 51 | public InetSocketAddress getAddress() { 52 | return address; 53 | } 54 | 55 | @Override 56 | protected void initChannel(DatagramChannel datagramChannel) throws Exception { 57 | this.channel = datagramChannel; 58 | channel.pipeline() 59 | .addLast("queryPacketCodec", new QueryPacketCodec()) 60 | .addLast("queryPacketHandler", new QueryPacketHandler(eventListener)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/QueryPacket.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public interface QueryPacket { 6 | 7 | void encode(ByteBuf buf); 8 | 9 | void decode(ByteBuf buf); 10 | 11 | int getSessionId(); 12 | 13 | void setSessionId(int sessionId); 14 | 15 | short getId(); 16 | } 17 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/QueryUtil.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import lombok.experimental.UtilityClass; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | 8 | @UtilityClass 9 | public class QueryUtil { 10 | public static final byte[] LONG_RESPONSE_PADDING_TOP = new byte[]{115, 112, 108, 105, 116, 110, 117, 109, 0, -128, 0}; 11 | public static final byte[] LONG_RESPONSE_PADDING_BOTTOM = new byte[]{1, 112, 108, 97, 121, 101, 114, 95, 0, 0}; 12 | 13 | public static void writeNullTerminatedByteArray(ByteBuf buf, byte[] array) { 14 | if (array != null) { 15 | buf.writeBytes(array); 16 | } 17 | buf.writeByte(0); // Null byte 18 | } 19 | 20 | public static String readNullTerminatedString(ByteBuf in) { 21 | StringBuilder read = new StringBuilder(); 22 | byte readIn; 23 | while ((readIn = in.readByte()) != '\0') { 24 | read.append((char) readIn); 25 | } 26 | return read.toString(); 27 | } 28 | 29 | public static void writeNullTerminatedString(ByteBuf buf, String string) { 30 | writeNullTerminatedByteArray(buf, string.getBytes(StandardCharsets.UTF_8)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/codec/QueryPacketCodec.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.socket.DatagramPacket; 7 | import io.netty.handler.codec.MessageToMessageCodec; 8 | import org.cloudburstmc.netty.handler.codec.query.QueryPacket; 9 | import org.cloudburstmc.netty.handler.codec.query.enveloped.DirectAddressedQueryPacket; 10 | import org.cloudburstmc.netty.handler.codec.query.packet.HandshakePacket; 11 | import org.cloudburstmc.netty.handler.codec.query.packet.StatisticsPacket; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public class QueryPacketCodec extends MessageToMessageCodec { 17 | private static final byte[] QUERY_SIGNATURE = new byte[]{(byte) 0xFE, (byte) 0xFD}; 18 | private static final int HANDSHAKE = 0x09; 19 | private static final short STATISTICS = 0x00; 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext channelHandlerContext, DirectAddressedQueryPacket packet, List list) throws Exception { 23 | try { 24 | ByteBuf buf = ByteBufAllocator.DEFAULT.ioBuffer(); 25 | buf.writeByte(packet.content().getId() & 0xFF); 26 | packet.content().encode(buf); 27 | list.add(new DatagramPacket(buf, packet.recipient(), packet.sender())); 28 | } finally { 29 | packet.release(); 30 | } 31 | } 32 | 33 | @Override 34 | protected void decode(ChannelHandlerContext channelHandlerContext, DatagramPacket packet, List list) throws Exception { 35 | ByteBuf buf = packet.content(); 36 | if (buf.readableBytes() < 3) { 37 | // not interested 38 | return; 39 | } 40 | buf.markReaderIndex(); 41 | 42 | byte[] prefix = new byte[2]; 43 | buf.readBytes(prefix); 44 | if (Arrays.equals(prefix, QUERY_SIGNATURE)) { 45 | short id = buf.readUnsignedByte(); 46 | QueryPacket networkPacket; 47 | switch (id) { 48 | case HANDSHAKE: 49 | networkPacket = new HandshakePacket(); 50 | break; 51 | case STATISTICS: 52 | networkPacket = new StatisticsPacket(); 53 | break; 54 | default: 55 | buf.resetReaderIndex(); 56 | return; 57 | } 58 | networkPacket.decode(buf); 59 | list.add(new DirectAddressedQueryPacket(networkPacket, packet.recipient(), packet.sender())); 60 | } else { 61 | buf.resetReaderIndex(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/enveloped/DirectAddressedQueryPacket.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query.enveloped; 2 | 3 | import io.netty.channel.DefaultAddressedEnvelope; 4 | import org.cloudburstmc.netty.handler.codec.query.QueryPacket; 5 | 6 | import java.net.InetSocketAddress; 7 | 8 | public class DirectAddressedQueryPacket extends DefaultAddressedEnvelope { 9 | public DirectAddressedQueryPacket(QueryPacket message, InetSocketAddress recipient, InetSocketAddress sender) { 10 | super(message, recipient, sender); 11 | } 12 | 13 | public DirectAddressedQueryPacket(QueryPacket message, InetSocketAddress recipient) { 14 | super(message, recipient); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/handler/QueryPacketHandler.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import org.cloudburstmc.netty.handler.codec.query.QueryEventListener; 6 | import org.cloudburstmc.netty.handler.codec.query.enveloped.DirectAddressedQueryPacket; 7 | import org.cloudburstmc.netty.handler.codec.query.packet.HandshakePacket; 8 | import org.cloudburstmc.netty.handler.codec.query.packet.StatisticsPacket; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.nio.ByteBuffer; 12 | import java.security.MessageDigest; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.util.Arrays; 15 | import java.util.Timer; 16 | import java.util.concurrent.ThreadLocalRandom; 17 | 18 | public class QueryPacketHandler extends SimpleChannelInboundHandler { 19 | private final QueryEventListener listener; 20 | private final Timer timer; 21 | private byte[] lastToken; 22 | private byte[] token = new byte[16]; 23 | 24 | public QueryPacketHandler(QueryEventListener listener) { 25 | this.listener = listener; 26 | this.timer = new Timer("QueryRegenerationTicker"); 27 | } 28 | 29 | 30 | @Override 31 | protected void channelRead0(ChannelHandlerContext ctx, DirectAddressedQueryPacket packet) throws Exception { 32 | if (packet.content() instanceof HandshakePacket) { 33 | HandshakePacket handshake = (HandshakePacket) packet.content(); 34 | handshake.setToken(getTokenString(packet.sender())); 35 | ctx.writeAndFlush(new DirectAddressedQueryPacket(handshake, packet.sender(), packet.recipient()), ctx.voidPromise()); 36 | } 37 | if (packet.content() instanceof StatisticsPacket) { 38 | StatisticsPacket statistics = (StatisticsPacket) packet.content(); 39 | if (!(statistics.getToken() == getTokenInt(packet.sender()))) { 40 | return; 41 | } 42 | 43 | QueryEventListener.Data data = listener.onQuery(packet.sender()); 44 | 45 | if (statistics.isFull()) { 46 | statistics.setPayload(data.getLongStats()); 47 | } else { 48 | statistics.setPayload(data.getShortStats()); 49 | } 50 | ctx.writeAndFlush(new DirectAddressedQueryPacket(statistics, packet.sender(), packet.recipient()), ctx.voidPromise()); 51 | } 52 | } 53 | 54 | public void refreshToken() { 55 | lastToken = token; 56 | ThreadLocalRandom.current().nextBytes(token); 57 | } 58 | 59 | private String getTokenString(InetSocketAddress socketAddress) { 60 | return Integer.toString(getTokenInt(socketAddress)); 61 | 62 | } 63 | 64 | private int getTokenInt(InetSocketAddress socketAddress) { 65 | return ByteBuffer.wrap(getToken(socketAddress)).getInt(); 66 | } 67 | 68 | private byte[] getToken(InetSocketAddress socketAddress) { 69 | MessageDigest digest; 70 | try { 71 | digest = MessageDigest.getInstance("MD5"); 72 | } catch (NoSuchAlgorithmException var3) { 73 | throw new InternalError("MD5 not supported", var3); 74 | } 75 | digest.update(socketAddress.toString().getBytes()); 76 | byte[] digested = digest.digest(token); 77 | return Arrays.copyOf(digested, 4); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/packet/HandshakePacket.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query.packet; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.cloudburstmc.netty.handler.codec.query.QueryPacket; 5 | import org.cloudburstmc.netty.handler.codec.query.QueryUtil; 6 | 7 | public class HandshakePacket implements QueryPacket { 8 | private static final short ID = 0x09; 9 | // Both 10 | private int sessionId; 11 | // Response 12 | private String token; 13 | 14 | @Override 15 | public void decode(ByteBuf buffer) { 16 | sessionId = buffer.readInt(); 17 | } 18 | 19 | @Override 20 | public void encode(ByteBuf buffer) { 21 | buffer.writeInt(sessionId); 22 | QueryUtil.writeNullTerminatedString(buffer, token); 23 | } 24 | 25 | @Override 26 | public short getId() { 27 | return ID; 28 | } 29 | 30 | @Override 31 | public int getSessionId() { 32 | return sessionId; 33 | } 34 | 35 | @Override 36 | public void setSessionId(int sessionId) { 37 | this.sessionId = sessionId; 38 | } 39 | 40 | public String getToken() { 41 | return token; 42 | } 43 | 44 | public void setToken(String token) { 45 | this.token = token; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /codec-query/src/main/java/org/cloudburstmc/netty/handler/codec/query/packet/StatisticsPacket.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.query.packet; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.cloudburstmc.netty.handler.codec.query.QueryPacket; 5 | 6 | public class StatisticsPacket implements QueryPacket { 7 | private static final short ID = 0x00; 8 | // Both 9 | private int sessionId; 10 | // Request 11 | private int token; 12 | private boolean full; 13 | // Response 14 | private ByteBuf payload; 15 | 16 | @Override 17 | public void decode(ByteBuf buffer) { 18 | sessionId = buffer.readInt(); 19 | token = buffer.readInt(); 20 | full = (buffer.isReadable()); 21 | buffer.skipBytes(buffer.readableBytes()); 22 | } 23 | 24 | @Override 25 | public void encode(ByteBuf buffer) { 26 | buffer.writeInt(sessionId); 27 | buffer.writeBytes(payload); 28 | } 29 | 30 | @Override 31 | public short getId() { 32 | return ID; 33 | } 34 | 35 | @Override 36 | public int getSessionId() { 37 | return sessionId; 38 | } 39 | 40 | @Override 41 | public void setSessionId(int sessionId) { 42 | this.sessionId = sessionId; 43 | } 44 | 45 | public int getToken() { 46 | return token; 47 | } 48 | 49 | public void setToken(int token) { 50 | this.token = token; 51 | } 52 | 53 | public boolean isFull() { 54 | return full; 55 | } 56 | 57 | public void setFull(boolean full) { 58 | this.full = full; 59 | } 60 | 61 | public ByteBuf getPayload() { 62 | return payload; 63 | } 64 | 65 | public void setPayload(ByteBuf payload) { 66 | this.payload = payload; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /codec-rcon/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 4.1.51.Final 7 | 8 | cloudburstmc-netty-parent 9 | org.cloudburstmc.netty 10 | 1.0.0.Final-SNAPSHOT 11 | 12 | 13 | netty-codec-rcon 14 | Netty/Codec/RCON 15 | 16 | 17 | 18 | io.netty 19 | netty-handler 20 | ${netty.version} 21 | 22 | 23 | -------------------------------------------------------------------------------- /codec-rcon/src/main/java/org/cloudburstmc/netty/handler/codec/rcon/RconCodec.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.rcon; 2 | 3 | import org.cloudburstmc.netty.handler.codec.rcon.RconMessage; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | import io.netty.channel.ChannelHandler.Sharable; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.ByteToMessageCodec; 9 | 10 | import java.util.List; 11 | 12 | @Sharable 13 | public class RconCodec extends ByteToMessageCodec { 14 | 15 | @Override 16 | protected void encode(ChannelHandlerContext channelHandlerContext, RconMessage rconMessage, ByteBuf byteBuf) throws Exception { 17 | byteBuf.writeIntLE(rconMessage.getId()); 18 | byteBuf.writeIntLE(rconMessage.getType()); 19 | ByteBufUtil.writeAscii(byteBuf, rconMessage.getBody()); 20 | // 2 null bytes 21 | byteBuf.writeByte(0); 22 | byteBuf.writeByte(0); 23 | } 24 | 25 | @Override 26 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 27 | int id = byteBuf.readIntLE(); 28 | int type = byteBuf.readIntLE(); 29 | String body = readNullTerminatedString(byteBuf); 30 | 31 | // Discard remaining bytes 32 | byteBuf.readerIndex(byteBuf.writerIndex()); 33 | 34 | list.add(new RconMessage(id, type, body)); 35 | } 36 | 37 | private static String readNullTerminatedString(ByteBuf in) { 38 | StringBuilder read = new StringBuilder(); 39 | byte readIn; 40 | while ((readIn = in.readByte()) != '\0') { 41 | read.append((char) readIn); 42 | } 43 | return read.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /codec-rcon/src/main/java/org/cloudburstmc/netty/handler/codec/rcon/RconEventListener.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.rcon; 2 | 3 | public interface RconEventListener { 4 | 5 | String onMessage(String message); 6 | } 7 | -------------------------------------------------------------------------------- /codec-rcon/src/main/java/org/cloudburstmc/netty/handler/codec/rcon/RconMessage.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.rcon; 2 | 3 | public class RconMessage { 4 | public static final int AUTH = 3; 5 | public static final int AUTH_RESPONSE = 2; 6 | public static final int EXECCOMMAND = 2; 7 | public static final int RESPONSE_VALUE = 0; 8 | 9 | private final int id; 10 | private final int type; 11 | private final String body; 12 | 13 | public RconMessage(int id, int type, String body) { 14 | this.id = id; 15 | this.type = type; 16 | this.body = body; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public int getType() { 24 | return type; 25 | } 26 | 27 | public String getBody() { 28 | return body; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /codec-rcon/src/main/java/org/cloudburstmc/netty/handler/codec/rcon/RconNetworkListener.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.rcon; 2 | 3 | import com.nukkitx.network.NetworkListener; 4 | import com.nukkitx.network.handler.ExceptionHandler; 5 | import org.cloudburstmc.netty.handler.codec.rcon.handler.RconHandler; 6 | import com.nukkitx.network.util.Bootstraps; 7 | import com.nukkitx.network.util.EventLoops; 8 | import com.nukkitx.network.util.NetworkThreadFactory; 9 | import io.netty.bootstrap.ServerBootstrap; 10 | import io.netty.buffer.ByteBufAllocator; 11 | import io.netty.channel.ChannelInitializer; 12 | import io.netty.channel.ChannelOption; 13 | import io.netty.channel.socket.SocketChannel; 14 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 15 | import io.netty.handler.codec.LengthFieldPrepender; 16 | import lombok.Getter; 17 | 18 | import java.net.InetSocketAddress; 19 | import java.nio.ByteOrder; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | public class RconNetworkListener extends ChannelInitializer implements NetworkListener { 25 | private final RconEventListener eventListener; 26 | private final InetSocketAddress address; 27 | private final ServerBootstrap bootstrap; 28 | @Getter 29 | private final ExecutorService commandExecutionService = Executors.newSingleThreadExecutor( 30 | NetworkThreadFactory.builder().daemon(true).format("RCON Command Executor").build()); 31 | private final byte[] password; 32 | private SocketChannel channel; 33 | 34 | public RconNetworkListener(RconEventListener eventListener, byte[] password, String address, int port) { 35 | this.eventListener = eventListener; 36 | this.password = password; 37 | this.address = new InetSocketAddress(address, port); 38 | 39 | bootstrap = new ServerBootstrap().option(ChannelOption.ALLOCATOR, ByteBufAllocator.DEFAULT).handler(this); 40 | 41 | Bootstraps.setupServerBootstrap(bootstrap); 42 | this.bootstrap.group(EventLoops.commonGroup()); 43 | } 44 | 45 | @Override 46 | public boolean bind() { 47 | return bootstrap.bind(address).awaitUninterruptibly().isSuccess(); 48 | } 49 | 50 | @Override 51 | public void close() { 52 | commandExecutionService.shutdown(); 53 | try { 54 | commandExecutionService.awaitTermination(10, TimeUnit.SECONDS); 55 | } catch (InterruptedException e) { 56 | // Ignore 57 | } 58 | bootstrap.config().group().shutdownGracefully(); 59 | if (channel != null) { 60 | channel.close().syncUninterruptibly(); 61 | } 62 | } 63 | 64 | @Override 65 | public InetSocketAddress getAddress() { 66 | return null; 67 | } 68 | 69 | @Override 70 | protected void initChannel(SocketChannel socketChannel) throws Exception { 71 | this.channel = socketChannel; 72 | 73 | channel.pipeline().addLast("lengthDecoder", new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 4096, 0, 4, 0, 4, true)); 74 | channel.pipeline().addLast("rconDecoder", new RconCodec()); 75 | channel.pipeline().addLast("rconHandler", new RconHandler(eventListener, password)); 76 | channel.pipeline().addLast("lengthPrepender", new LengthFieldPrepender(ByteOrder.LITTLE_ENDIAN, 4, 0, false)); 77 | channel.pipeline().addLast("exceptionHandler", new ExceptionHandler()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /codec-rcon/src/main/java/org/cloudburstmc/netty/handler/codec/rcon/handler/RconHandler.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.rcon.handler; 2 | 3 | import org.cloudburstmc.netty.handler.codec.rcon.RconEventListener; 4 | import org.cloudburstmc.netty.handler.codec.rcon.RconMessage; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.util.CharsetUtil; 9 | 10 | import java.security.MessageDigest; 11 | 12 | public class RconHandler extends SimpleChannelInboundHandler { 13 | private final RconEventListener eventListener; 14 | private final byte[] password; 15 | private boolean authed = false; 16 | 17 | public RconHandler(RconEventListener eventListener, byte[] password) { 18 | this.eventListener = eventListener; 19 | this.password = password; 20 | } 21 | 22 | @Override 23 | protected void channelRead0(ChannelHandlerContext ctx, RconMessage rconMessage) throws Exception { 24 | if (!authed) { 25 | if (rconMessage.getType() != RconMessage.AUTH) { 26 | return; 27 | } 28 | 29 | byte[] sentPassword = rconMessage.getBody().getBytes(CharsetUtil.UTF_8); 30 | 31 | ctx.channel().writeAndFlush(new RconMessage(rconMessage.getId(), RconMessage.RESPONSE_VALUE, "")); 32 | 33 | if (MessageDigest.isEqual(password, sentPassword)) { 34 | authed = true; 35 | ctx.channel().writeAndFlush(new RconMessage(rconMessage.getId(), RconMessage.AUTH_RESPONSE, "")); 36 | } else { 37 | ctx.channel().writeAndFlush(new RconMessage(-1, RconMessage.AUTH_RESPONSE, "")); 38 | } 39 | } else if (rconMessage.getType() == RconMessage.EXECCOMMAND) { 40 | Channel channel = ctx.channel(); 41 | 42 | String output = eventListener.onMessage(rconMessage.getBody()); 43 | channel.writeAndFlush(new RconMessage(rconMessage.getId(), RconMessage.RESPONSE_VALUE, output), ctx.voidPromise()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2023 CloudburstMC 3 | # 4 | # CloudburstMC licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # 16 | # We follow JBoss versioning https://developer.jboss.org/docs/DOC-10725 17 | version=1.0.0.CR3-SNAPSHOT 18 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | netty = "4.1.101.Final" 3 | junit = "5.9.2" 4 | 5 | 6 | [libraries] 7 | netty-common = { group = "io.netty", name = "netty-common", version.ref = "netty" } 8 | netty-buffer = { group = "io.netty", name = "netty-buffer", version.ref = "netty" } 9 | netty-codec = { group = "io.netty", name = "netty-codec", version.ref = "netty" } 10 | netty-transport = { group = "io.netty", name = "netty-transport", version.ref = "netty" } 11 | netty-transport-native-unix-common = { group = "io.netty", name = "netty-transport-native-unix-common", version.ref = "netty" } 12 | 13 | expiringmap = { group = "net.jodah", name = "expiringmap", version = "0.5.10" } 14 | 15 | # Test dependencies 16 | junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" } 17 | junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } 18 | junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } 19 | 20 | 21 | [bundles] 22 | netty = [ "netty-common", "netty-buffer", "netty-codec", "netty-transport", "netty-transport-native-unix-common" ] 23 | junit = [ "junit-jupiter-engine", "junit-jupiter-api", "junit-jupiter-params" ] 24 | 25 | 26 | [plugins] 27 | 28 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudburstMC/Network/8cbe16d914cb633620aefb90b472a183daf55f1e/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.0.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | rootProject.name = "network" 18 | 19 | plugins { 20 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" 21 | } 22 | 23 | include("transport-raknet") 24 | -------------------------------------------------------------------------------- /transport-raknet/README.md: -------------------------------------------------------------------------------- 1 | # netty-transport-raknet 2 | -------------------------------------------------------------------------------- /transport-raknet/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | description = "RakNet transport for Netty" 18 | 19 | dependencies { 20 | api(libs.bundles.netty) 21 | api(libs.expiringmap) 22 | 23 | testImplementation(libs.bundles.junit) 24 | } 25 | 26 | tasks.jar { 27 | manifest.attributes["Automatic-Module-Name"] = "org.cloudburstmc.netty.transport.raknet" 28 | } 29 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/proxy/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * Channel related classes 19 | */ 20 | package org.cloudburstmc.netty.channel.proxy; -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelPipeline; 21 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelConfig; 22 | 23 | public interface RakChannel extends Channel { 24 | 25 | ChannelPipeline rakPipeline(); 26 | 27 | @Override 28 | RakChannelConfig config(); 29 | } 30 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakChannelFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelException; 21 | import io.netty.channel.ChannelFactory; 22 | import io.netty.channel.socket.DatagramChannel; 23 | import io.netty.util.internal.StringUtil; 24 | 25 | import java.lang.reflect.Constructor; 26 | import java.util.Objects; 27 | import java.util.function.Consumer; 28 | import java.util.function.Function; 29 | 30 | public class RakChannelFactory implements ChannelFactory { 31 | 32 | private final Class channelClass; 33 | private final Function constructor; 34 | private final Constructor datagramConstructor; 35 | private final Consumer parentConsumer; 36 | 37 | private RakChannelFactory(Class channelClass, Function constructor, Class datagramClass, 38 | Consumer parentConsumer) { 39 | Objects.requireNonNull(channelClass, "channelClass"); 40 | Objects.requireNonNull(datagramClass, "datagramClass"); 41 | Objects.requireNonNull(constructor, "constructor"); 42 | this.channelClass = channelClass; 43 | this.constructor = constructor; 44 | 45 | try { 46 | this.datagramConstructor = datagramClass.getConstructor(); 47 | } catch (NoSuchMethodException e) { 48 | throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(datagramClass) + 49 | " does not have a public non-arg constructor", e); 50 | } 51 | this.parentConsumer = parentConsumer; 52 | } 53 | 54 | public static RakChannelFactory server(Class clazz) { 55 | return new RakChannelFactory<>(RakServerChannel.class, RakServerChannel::new, clazz, null); 56 | } 57 | 58 | public static RakChannelFactory server(Class clazz, Consumer parentConsumer) { 59 | return new RakChannelFactory<>(RakServerChannel.class, RakServerChannel::new, clazz, parentConsumer); 60 | } 61 | 62 | public static RakChannelFactory server(Class clazz, Consumer parentConsumer, Consumer childConsumer) { 63 | return new RakChannelFactory<>(RakServerChannel.class, ch -> new RakServerChannel(ch, childConsumer), clazz, parentConsumer); 64 | } 65 | 66 | public static RakChannelFactory client(Class clazz) { 67 | return new RakChannelFactory<>(RakClientChannel.class, RakClientChannel::new, clazz, null); 68 | } 69 | 70 | public static RakChannelFactory client(Class clazz, Consumer parentConsumer) { 71 | return new RakChannelFactory<>(RakClientChannel.class, RakClientChannel::new, clazz, parentConsumer); 72 | } 73 | 74 | @Override 75 | public T newChannel() { 76 | try { 77 | DatagramChannel channel = datagramConstructor.newInstance(); 78 | if (this.parentConsumer != null) { 79 | this.parentConsumer.accept(channel); 80 | } 81 | return constructor.apply(channel); 82 | } catch (Throwable t) { 83 | throw new ChannelException("Unable to create Channel from class " + this.channelClass, t); 84 | } 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return StringUtil.simpleClassName(RakChannelFactory.class) + 90 | '(' + StringUtil.simpleClassName(this.channelClass) + ".class, " + 91 | StringUtil.simpleClassName(datagramConstructor.getDeclaringClass()) + ".class)"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakChannelPipeline.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.DefaultChannelPipeline; 22 | import io.netty.util.ReferenceCountUtil; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 26 | 27 | public class RakChannelPipeline extends DefaultChannelPipeline { 28 | 29 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakChannelPipeline.class); 30 | 31 | private final RakChildChannel child; 32 | 33 | protected RakChannelPipeline(Channel parent, RakChildChannel child) { 34 | super(parent); 35 | this.child = child; 36 | } 37 | 38 | @Override 39 | protected void onUnhandledInboundChannelActive() { 40 | } 41 | 42 | @Override 43 | protected void onUnhandledInboundChannelInactive() { 44 | if (this.child.isActive()) { 45 | this.child.setActive(false); 46 | this.child.pipeline().fireChannelInactive(); 47 | } 48 | } 49 | 50 | @Override 51 | protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) { 52 | try { 53 | final Object message = msg instanceof EncapsulatedPacket ? ((EncapsulatedPacket) msg).toMessage() : msg; 54 | ReferenceCountUtil.retain(message); 55 | if (this.child.eventLoop().inEventLoop()) { 56 | this.child.pipeline().fireChannelRead(message).fireChannelReadComplete(); 57 | } else { 58 | this.child.eventLoop().execute(() -> { 59 | this.child.pipeline() 60 | .fireChannelRead(message) 61 | .fireChannelReadComplete(); 62 | }); 63 | } 64 | } finally { 65 | ReferenceCountUtil.release(msg); 66 | } 67 | } 68 | 69 | @Override 70 | protected void onUnhandledInboundUserEventTriggered(Object evt) { 71 | this.child.pipeline().fireUserEventTriggered(evt); 72 | if (evt instanceof RakDisconnectReason) { 73 | this.child.close(); 74 | } 75 | } 76 | 77 | @Override 78 | protected void onUnhandledInboundException(Throwable cause) { 79 | log.error("Exception thrown in RakNet pipeline", cause); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakChildChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.*; 20 | import io.netty.util.ReferenceCountUtil; 21 | import org.cloudburstmc.netty.channel.raknet.config.DefaultRakSessionConfig; 22 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelConfig; 23 | import org.cloudburstmc.netty.handler.codec.raknet.common.*; 24 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakChildDatagramHandler; 25 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOnlineInitialHandler; 26 | 27 | import java.net.InetSocketAddress; 28 | import java.net.SocketAddress; 29 | import java.nio.channels.ClosedChannelException; 30 | import java.nio.channels.NonWritableChannelException; 31 | import java.util.function.Consumer; 32 | 33 | public class RakChildChannel extends AbstractChannel implements RakChannel { 34 | 35 | private static final ChannelMetadata metadata = new ChannelMetadata(true); 36 | 37 | private final RakChannelConfig config; 38 | private final InetSocketAddress remoteAddress; 39 | private final InetSocketAddress localAddress; 40 | private final DefaultChannelPipeline rakPipeline; 41 | private volatile boolean open = true; 42 | private volatile boolean active; 43 | 44 | RakChildChannel(InetSocketAddress remoteAddress, InetSocketAddress localAddress, RakServerChannel parent, long guid, int version, int mtu, Consumer childConsumer) { 45 | super(parent); 46 | this.remoteAddress = remoteAddress; 47 | this.localAddress = localAddress; 48 | this.config = new DefaultRakSessionConfig(this); 49 | this.config.setGuid(guid); 50 | this.config.setProtocolVersion(version); 51 | this.config.setMtu(mtu); 52 | // Allow user to configure the child channel before we initialize pipeline 53 | // This is not the same as bootstrap.childOption() as Bootstrap does not allow setting options per channel 54 | if (childConsumer != null) { 55 | childConsumer.accept(this); 56 | } 57 | // Create an internal pipeline for RakNet session logic to take place. We use the parent channel to ensure 58 | // this all occurs on the parent event loop so the connection is not slowed down by any user code. 59 | // (compression, encryption, etc.) 60 | this.rakPipeline = new RakChannelPipeline(parent, this); 61 | this.rakPipeline.addLast(RakChildDatagramHandler.NAME, new RakChildDatagramHandler(this)); 62 | 63 | // Setup session/online phase 64 | RakSessionCodec sessionCodec = new RakSessionCodec(this); 65 | this.rakPipeline.addLast(RakDatagramCodec.NAME, new RakDatagramCodec()); 66 | this.rakPipeline.addLast(RakAcknowledgeHandler.NAME, new RakAcknowledgeHandler(sessionCodec)); 67 | this.rakPipeline.addLast(RakSessionCodec.NAME, sessionCodec); 68 | // This handler auto-removes once ConnectionRequest is received 69 | this.rakPipeline.addLast(ConnectedPingHandler.NAME, new ConnectedPingHandler()); 70 | this.rakPipeline.addLast(ConnectedPongHandler.NAME, new ConnectedPongHandler(sessionCodec)); 71 | this.rakPipeline.addLast(DisconnectNotificationHandler.NAME, DisconnectNotificationHandler.INSTANCE); 72 | this.rakPipeline.addLast(RakServerOnlineInitialHandler.NAME, new RakServerOnlineInitialHandler(this)); 73 | this.rakPipeline.addLast(RakUnhandledMessagesQueue.NAME, new RakUnhandledMessagesQueue(this)); 74 | this.rakPipeline.fireChannelRegistered(); 75 | this.rakPipeline.fireChannelActive(); 76 | } 77 | 78 | @Override 79 | public ChannelPipeline rakPipeline() { 80 | return rakPipeline; 81 | } 82 | 83 | @Override 84 | public SocketAddress localAddress0() { 85 | return this.localAddress; 86 | } 87 | 88 | @Override 89 | public SocketAddress remoteAddress0() { 90 | return this.remoteAddress; 91 | } 92 | 93 | @Override 94 | public InetSocketAddress localAddress() { 95 | return (InetSocketAddress) super.localAddress(); 96 | } 97 | 98 | @Override 99 | public InetSocketAddress remoteAddress() { 100 | return (InetSocketAddress) super.remoteAddress(); 101 | } 102 | 103 | @Override 104 | public RakChannelConfig config() { 105 | return this.config; 106 | } 107 | 108 | @Override 109 | public ChannelMetadata metadata() { 110 | return metadata; 111 | } 112 | 113 | @Override 114 | protected void doBind(SocketAddress socketAddress) throws Exception { 115 | throw new UnsupportedOperationException("Can not bind child channel!"); 116 | } 117 | 118 | @Override 119 | protected void doBeginRead() throws Exception { 120 | // Ignore 121 | } 122 | 123 | @Override 124 | protected void doWrite(ChannelOutboundBuffer in) throws Exception { 125 | if (!this.open) { 126 | throw new ClosedChannelException(); 127 | } else if (!active) { 128 | throw new NonWritableChannelException(); 129 | } 130 | ClosedChannelException exception = null; 131 | for (; ; ) { 132 | Object msg = in.current(); 133 | if (msg == null) { 134 | break; 135 | } 136 | try { 137 | if (this.parent().isOpen()) { 138 | this.rakPipeline.write(ReferenceCountUtil.retain(msg)); 139 | in.remove(); 140 | } else { 141 | if (exception == null) { 142 | exception = new ClosedChannelException(); 143 | } 144 | in.remove(exception); 145 | } 146 | } catch (Throwable cause) { 147 | in.remove(cause); 148 | } 149 | } 150 | this.rakPipeline.flush(); 151 | } 152 | 153 | public void setActive(boolean active) { 154 | this.active = active; 155 | } 156 | 157 | @Override 158 | protected void doDisconnect() throws Exception { 159 | this.close(); 160 | } 161 | 162 | @Override 163 | protected void doClose() throws Exception { 164 | this.open = false; 165 | } 166 | 167 | @Override 168 | public boolean isActive() { 169 | return this.isOpen() && this.active; 170 | } 171 | 172 | @Override 173 | public boolean isOpen() { 174 | return this.open; 175 | } 176 | 177 | @Override 178 | protected boolean isCompatible(EventLoop eventLoop) { 179 | return true; 180 | } 181 | 182 | @Override 183 | protected AbstractUnsafe newUnsafe() { 184 | return new AbstractUnsafe() { 185 | @Override 186 | public void connect(SocketAddress socketAddress, SocketAddress socketAddress1, ChannelPromise channelPromise) { 187 | throw new UnsupportedOperationException("Can not connect child channel!"); 188 | } 189 | }; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakClientChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.ChannelMetadata; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.channel.ChannelPromise; 22 | import io.netty.channel.socket.DatagramChannel; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | import org.cloudburstmc.netty.channel.proxy.ProxyChannel; 26 | import org.cloudburstmc.netty.channel.raknet.config.DefaultRakClientConfig; 27 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelConfig; 28 | import org.cloudburstmc.netty.handler.codec.raknet.ProxyInboundRouter; 29 | import org.cloudburstmc.netty.handler.codec.raknet.client.RakClientProxyRouteHandler; 30 | import org.cloudburstmc.netty.handler.codec.raknet.client.RakClientRouteHandler; 31 | import org.cloudburstmc.netty.handler.codec.raknet.common.*; 32 | 33 | public class RakClientChannel extends ProxyChannel implements RakChannel { 34 | 35 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientChannel.class); 36 | private static final ChannelMetadata metadata = new ChannelMetadata(true); 37 | 38 | /** 39 | * Implementation of simple RakClient which is able to connect to only one server during lifetime. 40 | */ 41 | private final RakChannelConfig config; 42 | private final ChannelPromise connectPromise; 43 | 44 | public RakClientChannel(DatagramChannel channel) { 45 | super(channel); 46 | this.config = new DefaultRakClientConfig(this); 47 | 48 | this.pipeline().addLast(RakClientRouteHandler.NAME, new RakClientRouteHandler(this)); 49 | // Transforms DatagramPacket to ByteBuf if channel has been already connected 50 | this.rakPipeline().addFirst(RakClientProxyRouteHandler.NAME, new RakClientProxyRouteHandler(this)); 51 | // Encodes to buffer and sends RakPing. 52 | this.rakPipeline().addBefore(ProxyInboundRouter.NAME, UnconnectedPingEncoder.NAME, new UnconnectedPingEncoder(this)); 53 | // Decodes received unconnected pong to RakPong. 54 | this.rakPipeline().addAfter(UnconnectedPingEncoder.NAME, UnconnectedPongDecoder.NAME, new UnconnectedPongDecoder(this)); 55 | 56 | this.connectPromise = this.newPromise(); 57 | this.connectPromise.addListener(future -> { 58 | if (future.isSuccess()) { 59 | this.onConnectionEstablished(); 60 | } else { 61 | this.close(); 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * Setup online phase handlers 68 | */ 69 | private void onConnectionEstablished() { 70 | // Send fireChannelActive() to user pipeline 71 | this.pipeline().fireChannelActive(); 72 | } 73 | 74 | @Override 75 | public RakChannelConfig config() { 76 | return this.config; 77 | } 78 | 79 | public ChannelPromise getConnectPromise() { 80 | return this.connectPromise; 81 | } 82 | 83 | @Override 84 | public boolean isActive() { 85 | return super.isActive() && this.connectPromise.isSuccess(); 86 | } 87 | 88 | @Override 89 | public ChannelPipeline rakPipeline() { 90 | return this.parent().pipeline(); 91 | } 92 | 93 | @Override 94 | public ChannelMetadata metadata() { 95 | return metadata; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 20 | import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; 21 | 22 | import java.net.Inet4Address; 23 | import java.net.Inet6Address; 24 | import java.net.InetSocketAddress; 25 | 26 | public class RakConstants { 27 | 28 | public static final byte RAKNET_PROTOCOL_VERSION = 11; // Mojang's version. 29 | public static final int MINIMUM_MTU_SIZE = 576; 30 | public static final int MAXIMUM_MTU_SIZE = 1400; 31 | public static final Integer[] MTU_SIZES = new Integer[]{MAXIMUM_MTU_SIZE, 1200, MINIMUM_MTU_SIZE}; 32 | /** 33 | * Maximum amount of ordering channels as defined in vanilla RakNet. 34 | */ 35 | public static final int MAXIMUM_ORDERING_CHANNELS = 16; 36 | /** 37 | * Maximum size of an {@link EncapsulatedPacket} header. 38 | */ 39 | public static final int MAXIMUM_ENCAPSULATED_HEADER_SIZE = 28; 40 | 41 | public static final int UDP_HEADER_SIZE = 8; 42 | 43 | public static final int RAKNET_DATAGRAM_HEADER_SIZE = 4; 44 | 45 | public static final int MAXIMUM_CONNECTION_ATTEMPTS = 10; 46 | 47 | public static final int TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS = 1000; 48 | /** 49 | * Time after {@link RakSessionCodec} is closed due to no activity. 50 | */ 51 | public static final int SESSION_TIMEOUT_MS = 10000; 52 | /** 53 | * Time after {@link RakSessionCodec} is refreshed due to no activity. 54 | */ 55 | public static final int SESSION_STALE_MS = 5000; 56 | /** 57 | * A number of datagram packets each address can send within one RakNet tick (10ms) 58 | */ 59 | public static final int DEFAULT_PACKET_LIMIT = 120; 60 | /** 61 | * A number of all datagrams that will be handled within one RakNet tick before server starts dropping any incoming data. 62 | */ 63 | public static final int DEFAULT_GLOBAL_PACKET_LIMIT = 100000; 64 | /* 65 | * Flags 66 | */ 67 | public static final byte FLAG_VALID = (byte) 0b10000000; 68 | public static final byte FLAG_ACK = (byte) 0b01000000; 69 | public static final byte FLAG_HAS_B_AND_AS = (byte) 0b00100000; 70 | public static final byte FLAG_NACK = (byte) 0b00100000; 71 | public static final byte FLAG_PACKET_PAIR = (byte) 0b00010000; 72 | public static final byte FLAG_CONTINUOUS_SEND = (byte) 0b00001000; 73 | public static final byte FLAG_NEEDS_B_AND_AS = (byte) 0b00000100; 74 | 75 | /* 76 | * Packet IDs 77 | */ 78 | public static final short ID_CONNECTED_PING = 0x00; 79 | public static final short ID_UNCONNECTED_PING = 0x01; 80 | public static final short ID_UNCONNECTED_PING_OPEN_CONNECTIONS = 0x02; 81 | public static final short ID_CONNECTED_PONG = 0x03; 82 | public static final short ID_DETECT_LOST_CONNECTION = 0x04; 83 | public static final short ID_OPEN_CONNECTION_REQUEST_1 = 0x05; 84 | public static final short ID_OPEN_CONNECTION_REPLY_1 = 0x06; 85 | public static final short ID_OPEN_CONNECTION_REQUEST_2 = 0x07; 86 | public static final short ID_OPEN_CONNECTION_REPLY_2 = 0x08; 87 | public static final short ID_CONNECTION_REQUEST = 0x09; 88 | public static final short ID_CONNECTION_REQUEST_ACCEPTED = 0x10; 89 | public static final short ID_CONNECTION_REQUEST_FAILED = 0x11; 90 | public static final short ID_ALREADY_CONNECTED = 0x12; 91 | public static final short ID_NEW_INCOMING_CONNECTION = 0x13; 92 | public static final short ID_NO_FREE_INCOMING_CONNECTIONS = 0x14; 93 | public static final short ID_DISCONNECTION_NOTIFICATION = 0x15; 94 | public static final short ID_CONNECTION_LOST = 0x16; 95 | public static final short ID_CONNECTION_BANNED = 0x17; 96 | public static final short ID_INCOMPATIBLE_PROTOCOL_VERSION = 0x19; 97 | public static final short ID_IP_RECENTLY_CONNECTED = 0x1a; 98 | public static final short ID_TIMESTAMP = 0x1b; 99 | public static final short ID_UNCONNECTED_PONG = 0x1c; 100 | public static final short ID_ADVERTISE_SYSTEM = 0x1d; 101 | public static final short ID_USER_PACKET_ENUM = 0x80; 102 | 103 | /** 104 | * Magic used to identify RakNet packets 105 | */ 106 | public static final byte[] DEFAULT_UNCONNECTED_MAGIC = new byte[]{ 107 | 0, -1, -1, 0, -2, -2, -2, -2, -3, -3, -3, -3, 18, 52, 86, 120 108 | }; 109 | 110 | /* 111 | * Congestion Control related constants 112 | */ 113 | public static final long CC_MAXIMUM_THRESHOLD = 2000; 114 | public static final long CC_ADDITIONAL_VARIANCE = 30; 115 | public static final long CC_SYN = 10; 116 | 117 | 118 | /* 119 | * IP constants 120 | */ 121 | public static final int IPV4_MESSAGE_SIZE = 7; 122 | public static final int IPV6_MESSAGE_SIZE = 29; 123 | 124 | public static final InetSocketAddress LOOPBACK_V4 = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 0); 125 | public static final InetSocketAddress LOOPBACK_V6 = new InetSocketAddress(Inet6Address.getLoopbackAddress(), 0); 126 | public static final InetSocketAddress LOCAL_ADDRESS = new InetSocketAddress(0); 127 | public static final InetSocketAddress[] LOCAL_IP_ADDRESSES_V4 = new InetSocketAddress[10]; 128 | public static final InetSocketAddress[] LOCAL_IP_ADDRESSES_V6 = new InetSocketAddress[10]; 129 | 130 | 131 | static { 132 | LOCAL_IP_ADDRESSES_V4[0] = LOOPBACK_V4; 133 | LOCAL_IP_ADDRESSES_V6[0] = LOOPBACK_V6; 134 | 135 | for (int i = 1; i < 10; i++) { 136 | LOCAL_IP_ADDRESSES_V4[i] = new InetSocketAddress("0.0.0.0", 0); 137 | LOCAL_IP_ADDRESSES_V6[i] = new InetSocketAddress("::0", 0); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakDisconnectReason.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakDisconnectReason { 20 | CLOSED_BY_REMOTE_PEER, 21 | SHUTTING_DOWN, 22 | DISCONNECTED, 23 | TIMED_OUT, 24 | CONNECTION_REQUEST_FAILED, 25 | ALREADY_CONNECTED, 26 | NO_FREE_INCOMING_CONNECTIONS, 27 | INCOMPATIBLE_PROTOCOL_VERSION, 28 | IP_RECENTLY_CONNECTED, 29 | BAD_PACKET, 30 | QUEUE_TOO_LONG 31 | } 32 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakEvent { 20 | NEW_INCOMING_CONNECTION 21 | } -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakOfflineState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakOfflineState { 20 | HANDSHAKE_1, 21 | HANDSHAKE_2, 22 | HANDSHAKE_COMPLETED; 23 | } 24 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakPing.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | 21 | import java.net.InetSocketAddress; 22 | 23 | public class RakPing { 24 | 25 | private final long pingTime; 26 | private final InetSocketAddress sender; 27 | 28 | public RakPing(long pingTime, InetSocketAddress sender) { 29 | this.pingTime = pingTime; 30 | this.sender = sender; 31 | } 32 | 33 | public long getPingTime() { 34 | return this.pingTime; 35 | } 36 | 37 | 38 | public InetSocketAddress getSender() { 39 | return this.sender; 40 | } 41 | 42 | public RakPong reply(long guid, ByteBuf pongData) { 43 | return new RakPong(this.pingTime, guid, pongData, this.sender); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakPong.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.util.AbstractReferenceCounted; 21 | import io.netty.util.ReferenceCountUtil; 22 | 23 | import java.net.InetSocketAddress; 24 | 25 | public class RakPong extends AbstractReferenceCounted { 26 | 27 | private final long pingTime; 28 | private final long guid; 29 | private final ByteBuf pongData; 30 | private final InetSocketAddress sender; 31 | 32 | public RakPong(long pingTime, long guid, ByteBuf pongData, InetSocketAddress sender) { 33 | this.pingTime = pingTime; 34 | this.guid = guid; 35 | this.pongData = pongData; 36 | this.sender = sender; 37 | } 38 | 39 | public long getPingTime() { 40 | return this.pingTime; 41 | } 42 | 43 | public long getGuid() { 44 | return this.guid; 45 | } 46 | 47 | public ByteBuf getPongData() { 48 | return this.pongData; 49 | } 50 | 51 | public InetSocketAddress getSender() { 52 | return this.sender; 53 | } 54 | 55 | @Override 56 | protected void deallocate() { 57 | ReferenceCountUtil.release(this.pongData); 58 | } 59 | 60 | @Override 61 | public RakPong touch(Object hint) { 62 | if (pongData != null) { 63 | pongData.touch(hint); 64 | } 65 | return this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakPriority.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakPriority { 20 | IMMEDIATE, 21 | HIGH, 22 | NORMAL, 23 | LOW 24 | } 25 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakReliability.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakReliability { 20 | UNRELIABLE(false, false, false, false), 21 | UNRELIABLE_SEQUENCED(false, false, true, false), 22 | RELIABLE(true, false, false, false), 23 | RELIABLE_ORDERED(true, true, false, false), 24 | RELIABLE_SEQUENCED(true, false, true, false), 25 | UNRELIABLE_WITH_ACK_RECEIPT(false, false, false, true), 26 | UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT(false, false, true, true), 27 | RELIABLE_WITH_ACK_RECEIPT(true, false, false, true), 28 | RELIABLE_ORDERED_WITH_ACK_RECEIPT(true, true, false, true), 29 | RELIABLE_SEQUENCED_WITH_ACK_RECEIPT(true, false, true, true); 30 | 31 | private static final RakReliability[] VALUES = values(); 32 | 33 | final boolean reliable; 34 | final boolean ordered; 35 | final boolean sequenced; 36 | final boolean withAckReceipt; 37 | final int size; 38 | 39 | RakReliability(boolean reliable, boolean ordered, boolean sequenced, boolean withAckReceipt) { 40 | this.reliable = reliable; 41 | this.ordered = ordered; 42 | this.sequenced = sequenced; 43 | this.withAckReceipt = withAckReceipt; 44 | 45 | int size = 0; 46 | if (this.reliable) { 47 | size += 3; 48 | } 49 | 50 | if (this.sequenced) { 51 | size += 3; 52 | } 53 | 54 | if (this.ordered) { 55 | size += 4; 56 | } 57 | this.size = size; 58 | } 59 | 60 | public static RakReliability fromId(int id) { 61 | if (id < 0 || id > 7) { 62 | return null; 63 | } 64 | return VALUES[id]; 65 | } 66 | 67 | public int getSize() { 68 | return size; 69 | } 70 | 71 | public boolean isOrdered() { 72 | return ordered; 73 | } 74 | 75 | public boolean isReliable() { 76 | return reliable; 77 | } 78 | 79 | public boolean isSequenced() { 80 | return sequenced; 81 | } 82 | 83 | public boolean isWithAckReceipt() { 84 | return withAckReceipt; 85 | } 86 | } -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakServerChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import io.netty.channel.ChannelFuture; 20 | import io.netty.channel.ChannelPromise; 21 | import io.netty.channel.ServerChannel; 22 | import io.netty.channel.socket.DatagramChannel; 23 | import io.netty.util.concurrent.GenericFutureListener; 24 | import io.netty.util.concurrent.PromiseCombiner; 25 | import io.netty.util.internal.logging.InternalLogger; 26 | import io.netty.util.internal.logging.InternalLoggerFactory; 27 | import org.cloudburstmc.netty.channel.proxy.ProxyChannel; 28 | import org.cloudburstmc.netty.channel.raknet.config.DefaultRakServerConfig; 29 | import org.cloudburstmc.netty.channel.raknet.config.RakServerChannelConfig; 30 | import org.cloudburstmc.netty.handler.codec.raknet.common.UnconnectedPongEncoder; 31 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler; 32 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerRateLimiter; 33 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerRouteHandler; 34 | import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerTailHandler; 35 | import org.cloudburstmc.netty.util.RakUtils; 36 | 37 | import java.net.InetAddress; 38 | import java.net.InetSocketAddress; 39 | import java.net.SocketAddress; 40 | import java.util.Map; 41 | import java.util.concurrent.ConcurrentHashMap; 42 | import java.util.concurrent.TimeUnit; 43 | import java.util.function.Consumer; 44 | 45 | public class RakServerChannel extends ProxyChannel implements ServerChannel { 46 | 47 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakServerChannel.class); 48 | 49 | private final RakServerChannelConfig config; 50 | private final Map childChannelMap = new ConcurrentHashMap<>(); 51 | private final Consumer childConsumer; 52 | 53 | public RakServerChannel(DatagramChannel channel) { 54 | this(channel, null); 55 | } 56 | 57 | public RakServerChannel(DatagramChannel channel, Consumer childConsumer) { 58 | super(channel); 59 | this.childConsumer = childConsumer; 60 | this.config = new DefaultRakServerConfig(this); 61 | // Default common handler of offline phase. Handles only raknet packets, forwards rest. 62 | this.pipeline().addLast(UnconnectedPongEncoder.NAME, UnconnectedPongEncoder.INSTANCE); 63 | if (this.config().getPacketLimit() > 0) { // No point in enabling this. 64 | this.pipeline().addLast(RakServerRateLimiter.NAME, new RakServerRateLimiter(this)); 65 | } 66 | this.pipeline().addLast(RakServerOfflineHandler.NAME, new RakServerOfflineHandler(this)); 67 | this.pipeline().addLast(RakServerRouteHandler.NAME, new RakServerRouteHandler(this)); 68 | this.pipeline().addLast(RakServerTailHandler.NAME, RakServerTailHandler.INSTANCE); 69 | } 70 | 71 | /** 72 | * Create new child channel assigned to remote address. 73 | * 74 | * @param address remote address of new connection. 75 | * @return RakChildChannel instance of new channel. 76 | */ 77 | public RakChildChannel createChildChannel(InetSocketAddress address, InetSocketAddress localAddress, 78 | long clientGuid, int protocolVersion, int mtu) { 79 | RakChildChannel existingChannel = this.childChannelMap.get(address); 80 | if (this.config().getSendCookie() && existingChannel != null) { 81 | // We know this player is coming from this IP address due to the cookie, so we can safely close the existing channel. 82 | existingChannel.close(); 83 | } else if (existingChannel != null) { 84 | // Could be spoofed, so we don't close the existing channel. 85 | return null; 86 | } 87 | 88 | RakChildChannel channel = new RakChildChannel(address, localAddress, this, clientGuid, protocolVersion, mtu, childConsumer); 89 | channel.closeFuture().addListener((GenericFutureListener) this::onChildClosed); 90 | // Fire channel thought ServerBootstrap, 91 | // register to eventLoop, assign default options and attributes 92 | this.pipeline().fireChannelRead(channel).fireChannelReadComplete(); 93 | this.childChannelMap.put(address, channel); 94 | 95 | if (this.config().getMetrics() != null) { 96 | this.config().getMetrics().channelOpen(address); 97 | } 98 | return channel; 99 | } 100 | 101 | public RakChildChannel getChildChannel(SocketAddress address) { 102 | return this.childChannelMap.get(address); 103 | } 104 | 105 | private void onChildClosed(ChannelFuture channelFuture) { 106 | RakChildChannel channel = (RakChildChannel) channelFuture.channel(); 107 | this.childChannelMap.remove(channel.remoteAddress()); 108 | 109 | if (this.config().getMetrics() != null) { 110 | this.config().getMetrics().channelClose(channel.remoteAddress()); 111 | } 112 | 113 | channel.rakPipeline().fireChannelInactive(); 114 | channel.rakPipeline().fireChannelUnregistered(); 115 | // Need to use reflection to destroy pipeline because 116 | // DefaultChannelPipeline.destroy() is only called when channel.isOpen() is false, 117 | // but the method is called on parent channel, and there is no other way to destroy pipeline. 118 | RakUtils.destroyChannelPipeline(channel.rakPipeline()); 119 | } 120 | 121 | @Override 122 | public void onCloseTriggered(ChannelPromise promise) { 123 | if (log.isTraceEnabled()) { 124 | log.trace("Closing RakServerChannel: {}", Thread.currentThread().getName(), new Throwable()); 125 | } 126 | PromiseCombiner combiner = new PromiseCombiner(this.eventLoop()); 127 | this.childChannelMap.values().forEach(channel -> combiner.add(channel.close())); 128 | 129 | ChannelPromise combinedPromise = this.newPromise(); 130 | combinedPromise.addListener(future -> super.onCloseTriggered(promise)); 131 | combiner.finish(combinedPromise); 132 | } 133 | 134 | public boolean tryBlockAddress(InetAddress address, long time, TimeUnit unit) { 135 | RakServerRateLimiter rateLimiter = this.pipeline().get(RakServerRateLimiter.class); 136 | if (rateLimiter != null) { 137 | return rateLimiter.blockAddress(address, time, unit); 138 | } 139 | return false; 140 | } 141 | 142 | @Override 143 | public RakServerChannelConfig config() { 144 | return this.config; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakSlidingWindow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | import org.cloudburstmc.netty.channel.raknet.packet.RakDatagramPacket; 20 | 21 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 22 | 23 | public class RakSlidingWindow { 24 | private final int mtu; 25 | private double cwnd; 26 | private double ssThresh; 27 | private double estimatedRTT = -1; 28 | private double lastRTT = -1; 29 | private double deviationRTT = -1; 30 | private long oldestUnsentAck; 31 | private long nextCongestionControlBlock; 32 | private boolean backoffThisBlock; 33 | private int unackedBytes; 34 | 35 | public RakSlidingWindow(int mtu) { 36 | this.mtu = mtu; 37 | this.cwnd = mtu; 38 | } 39 | 40 | public int getRetransmissionBandwidth() { 41 | return unackedBytes; 42 | } 43 | 44 | public int getTransmissionBandwidth() { 45 | if (this.unackedBytes <= this.cwnd) { 46 | return (int) (this.cwnd - this.unackedBytes); 47 | } else { 48 | return 0; 49 | } 50 | } 51 | 52 | public void onPacketReceived(long curTime) { 53 | if (this.oldestUnsentAck == 0) { 54 | this.oldestUnsentAck = curTime; 55 | } 56 | } 57 | 58 | public void onResend(long curSequenceIndex) { 59 | if (!this.backoffThisBlock && this.cwnd > this.mtu * 2D) { 60 | this.ssThresh = this.cwnd * 0.5D; 61 | 62 | if (this.ssThresh < this.mtu) { 63 | this.ssThresh = this.mtu; 64 | } 65 | this.cwnd = this.mtu; 66 | 67 | this.nextCongestionControlBlock = curSequenceIndex; 68 | this.backoffThisBlock = true; 69 | } 70 | } 71 | 72 | public void onNak() { 73 | if (!this.backoffThisBlock) { 74 | this.ssThresh = this.cwnd * 0.75D; 75 | } 76 | } 77 | 78 | public void onAck(long curTime, RakDatagramPacket datagram, long curSequenceIndex) { 79 | long rtt = curTime - datagram.getSendTime(); 80 | this.lastRTT = rtt; 81 | this.unackedBytes -= datagram.getSize(); 82 | 83 | if (this.estimatedRTT == -1) { 84 | this.estimatedRTT = rtt; 85 | this.deviationRTT = rtt; 86 | } else { 87 | double d = 0.05D; 88 | double difference = rtt - this.estimatedRTT; 89 | this.estimatedRTT += d * difference; 90 | this.deviationRTT += d * (Math.abs(difference) - this.deviationRTT); 91 | } 92 | 93 | boolean isNewCongestionControlPeriod = datagram.getSequenceIndex() > this.nextCongestionControlBlock; 94 | 95 | if (isNewCongestionControlPeriod) { 96 | this.backoffThisBlock = false; 97 | this.nextCongestionControlBlock = curSequenceIndex; 98 | } 99 | 100 | if (this.isInSlowStart()) { 101 | this.cwnd += this.mtu; 102 | 103 | if (this.cwnd > this.ssThresh && this.ssThresh != 0) { 104 | this.cwnd = this.ssThresh + this.mtu * this.mtu / this.cwnd; 105 | } 106 | } else if (isNewCongestionControlPeriod) { 107 | this.cwnd += this.mtu * this.mtu / this.cwnd; 108 | } 109 | } 110 | 111 | public void onReliableSend(RakDatagramPacket datagram) { 112 | this.unackedBytes += datagram.getSize(); 113 | } 114 | 115 | public boolean isInSlowStart() { 116 | return this.cwnd <= this.ssThresh || this.ssThresh == 0; 117 | } 118 | 119 | public void onSendAck() { 120 | this.oldestUnsentAck = 0; 121 | } 122 | 123 | @SuppressWarnings("ManualMinMaxCalculation") 124 | public long getRtoForRetransmission() { 125 | if (this.estimatedRTT == -1) { 126 | return CC_MAXIMUM_THRESHOLD; 127 | } 128 | 129 | long threshold = (long) ((2.0D * this.estimatedRTT + 4.0D * this.deviationRTT) + CC_ADDITIONAL_VARIANCE); 130 | 131 | return threshold > CC_MAXIMUM_THRESHOLD ? CC_MAXIMUM_THRESHOLD : threshold; 132 | } 133 | 134 | public double getRTT() { 135 | return this.estimatedRTT; 136 | } 137 | 138 | public boolean shouldSendAcks(long curTime) { 139 | long rto = this.getSenderRtoForAck(); 140 | 141 | return rto == -1 || curTime >= this.oldestUnsentAck + CC_SYN; 142 | } 143 | 144 | public long getSenderRtoForAck() { 145 | if (this.lastRTT == -1) { 146 | return -1; 147 | } else { 148 | return (long) (this.lastRTT + CC_SYN); 149 | } 150 | } 151 | 152 | public int getUnackedBytes() { 153 | return unackedBytes; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet; 18 | 19 | public enum RakState { 20 | UNCONNECTED, 21 | CONNECTED, 22 | DISCONNECTING, 23 | DISCONNECTED 24 | } 25 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.config; 18 | 19 | import io.netty.channel.ChannelConfig; 20 | 21 | public interface RakChannelConfig extends ChannelConfig { 22 | 23 | long getGuid(); 24 | 25 | RakChannelConfig setGuid(long guid); 26 | 27 | int getMtu(); 28 | 29 | RakChannelConfig setMtu(int mtu); 30 | 31 | int getProtocolVersion(); 32 | 33 | RakChannelConfig setProtocolVersion(int protocolVersion); 34 | 35 | int getOrderingChannels(); 36 | 37 | RakChannelConfig setOrderingChannels(int orderingChannels); 38 | 39 | RakChannelMetrics getMetrics(); 40 | 41 | RakChannelConfig setMetrics(RakChannelMetrics metrics); 42 | 43 | long getSessionTimeout(); 44 | 45 | RakChannelConfig setSessionTimeout(long timeout); 46 | 47 | boolean isAutoFlush(); 48 | 49 | void setAutoFlush(boolean enable); 50 | 51 | int getFlushInterval(); 52 | 53 | void setFlushInterval(int intervalMillis); 54 | 55 | void setMaxQueuedBytes(int maxQueuedBytes); 56 | 57 | int getMaxQueuedBytes(); 58 | } 59 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.config; 18 | 19 | import org.cloudburstmc.netty.channel.raknet.RakState; 20 | 21 | public interface RakChannelMetrics { 22 | 23 | default void bytesIn(int count) { 24 | } 25 | 26 | default void bytesOut(int count) { 27 | } 28 | 29 | default void rakDatagramsIn(int count) { 30 | } 31 | 32 | default void rakDatagramsOut(int count) { 33 | } 34 | 35 | default void encapsulatedIn(int count) { 36 | } 37 | 38 | default void encapsulatedOut(int count) { 39 | } 40 | 41 | default void rakStaleDatagrams(int count) { 42 | } 43 | 44 | default void ackIn(int count) { 45 | } 46 | 47 | default void ackOut(int count) { 48 | } 49 | 50 | default void nackOut(int count) { 51 | } 52 | 53 | default void nackIn(int count) { 54 | } 55 | 56 | default void stateChange(RakState state) { 57 | } 58 | 59 | default void queuedPacketBytes(int count) { 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerChannelConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.config; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelConfig; 21 | 22 | public interface RakServerChannelConfig extends ChannelConfig { 23 | 24 | int getMaxChannels(); 25 | 26 | RakServerChannelConfig setMaxChannels(int maxChannels); 27 | 28 | long getGuid(); 29 | 30 | RakServerChannelConfig setGuid(long guid); 31 | 32 | int[] getSupportedProtocols(); 33 | 34 | RakServerChannelConfig setSupportedProtocols(int[] supportedProtocols); 35 | 36 | int getMaxConnections(); 37 | 38 | RakServerChannelConfig setMaxConnections(int maxConnections); 39 | 40 | ByteBuf getUnconnectedMagic(); 41 | 42 | RakServerChannelConfig setUnconnectedMagic(ByteBuf unconnectedMagic); 43 | 44 | ByteBuf getAdvertisement(); 45 | 46 | RakServerChannelConfig setAdvertisement(ByteBuf advertisement); 47 | 48 | boolean getHandlePing(); 49 | 50 | RakServerChannelConfig setHandlePing(boolean handlePing); 51 | 52 | int getMaxMtu(); 53 | 54 | RakServerChannelConfig setMaxMtu(int mtu); 55 | 56 | int getMinMtu(); 57 | 58 | RakServerChannelConfig setMinMtu(int mtu); 59 | 60 | int getPacketLimit(); 61 | 62 | void setPacketLimit(int limit); 63 | 64 | int getGlobalPacketLimit(); 65 | 66 | void setGlobalPacketLimit(int limit); 67 | 68 | void setSendCookie(boolean sendCookie); 69 | 70 | boolean getSendCookie(); 71 | 72 | void setMetrics(RakServerMetrics metrics); 73 | 74 | RakServerMetrics getMetrics(); 75 | 76 | void setIpDontFragment(boolean ipDontFragment); 77 | 78 | boolean getIpDontFragment(); 79 | } 80 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.config; 18 | 19 | import java.net.InetAddress; 20 | import java.net.InetSocketAddress; 21 | 22 | public interface RakServerMetrics { 23 | 24 | default void channelOpen(InetSocketAddress address) { 25 | } 26 | 27 | default void channelClose(InetSocketAddress address) { 28 | } 29 | 30 | default void unconnectedPing(InetSocketAddress address) { 31 | } 32 | 33 | default void connectionInitPacket(InetSocketAddress address, int packetId) { 34 | } 35 | 36 | default void addressBlocked(InetAddress address) { 37 | } 38 | 39 | default void addressUnblocked(InetAddress address) { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.channel.raknet.config; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.channel.raknet; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/packet/RakDatagramPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.packet; 18 | 19 | import io.netty.util.AbstractReferenceCounted; 20 | import io.netty.util.internal.ObjectPool; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 26 | 27 | public class RakDatagramPacket extends AbstractReferenceCounted { 28 | 29 | private static final ObjectPool RECYCLER = ObjectPool.newPool(RakDatagramPacket::new); 30 | 31 | private final ObjectPool.Handle handle; 32 | private final List packets = new ArrayList<>(); 33 | private byte flags = FLAG_VALID | FLAG_NEEDS_B_AND_AS; 34 | private long sendTime; 35 | private long nextSend; 36 | private int sequenceIndex = -1; 37 | 38 | public static RakDatagramPacket newInstance() { 39 | return RECYCLER.get(); 40 | } 41 | 42 | private RakDatagramPacket(ObjectPool.Handle handle) { 43 | this.handle = handle; 44 | } 45 | 46 | @Override 47 | public RakDatagramPacket retain() { 48 | super.retain(); 49 | return this; 50 | } 51 | 52 | @Override 53 | public RakDatagramPacket retain(int increment) { 54 | super.retain(increment); 55 | return this; 56 | } 57 | 58 | @Override 59 | public RakDatagramPacket touch(Object hint) { 60 | for (EncapsulatedPacket packet : this.packets) { 61 | packet.touch(hint); 62 | } 63 | return this; 64 | } 65 | 66 | public boolean tryAddPacket(EncapsulatedPacket packet, int mtu) { 67 | if (this.getSize() + packet.getSize() > mtu - RAKNET_DATAGRAM_HEADER_SIZE) { 68 | return false; 69 | } 70 | 71 | this.packets.add(packet); 72 | if (packet.isSplit()) { 73 | flags |= FLAG_CONTINUOUS_SEND; 74 | } 75 | return true; 76 | } 77 | 78 | @Override 79 | public boolean release() { 80 | return super.release(); 81 | } 82 | 83 | @Override 84 | protected void deallocate() { 85 | for (EncapsulatedPacket packet : this.packets) { 86 | packet.release(); 87 | } 88 | this.packets.clear(); 89 | this.flags = FLAG_VALID | FLAG_NEEDS_B_AND_AS; 90 | this.sendTime = 0; 91 | this.nextSend = 0; 92 | this.sequenceIndex = -1; 93 | setRefCnt(1); 94 | this.handle.recycle(this); 95 | } 96 | 97 | public int getSize() { 98 | int size = RAKNET_DATAGRAM_HEADER_SIZE; 99 | for (EncapsulatedPacket packet : this.packets) { 100 | size += packet.getSize(); 101 | } 102 | return size; 103 | } 104 | 105 | public List getPackets() { 106 | return this.packets; 107 | } 108 | 109 | public byte getFlags() { 110 | return this.flags; 111 | } 112 | 113 | public void setFlags(byte flags) { 114 | this.flags = flags; 115 | } 116 | 117 | public long getSendTime() { 118 | return sendTime; 119 | } 120 | 121 | public void setSendTime(long sendTime) { 122 | this.sendTime = sendTime; 123 | } 124 | 125 | public long getNextSend() { 126 | return this.nextSend; 127 | } 128 | 129 | public void setNextSend(long nextSend) { 130 | this.nextSend = nextSend; 131 | } 132 | 133 | public int getSequenceIndex() { 134 | return this.sequenceIndex; 135 | } 136 | 137 | public void setSequenceIndex(int sequenceIndex) { 138 | this.sequenceIndex = sequenceIndex; 139 | } 140 | 141 | @Override 142 | public String toString() { 143 | return "RakDatagramPacket{" + 144 | "handle=" + handle + 145 | ", packets=" + packets + 146 | ", flags=" + flags + 147 | ", sendTime=" + sendTime + 148 | ", nextSend=" + nextSend + 149 | ", sequenceIndex=" + sequenceIndex + 150 | '}'; 151 | } 152 | } -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/packet/RakMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.channel.raknet.packet; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.DefaultByteBufHolder; 21 | import org.cloudburstmc.netty.channel.raknet.RakPriority; 22 | import org.cloudburstmc.netty.channel.raknet.RakReliability; 23 | 24 | /** 25 | * Representation of a RakNet Packet 26 | */ 27 | public final class RakMessage extends DefaultByteBufHolder { 28 | private final RakReliability reliability; 29 | private final RakPriority priority; 30 | private final int channel; 31 | 32 | public RakMessage(ByteBuf payloadBuffer) { 33 | this(payloadBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL, 0); 34 | } 35 | 36 | public RakMessage(ByteBuf payloadBuffer, RakReliability reliability) { 37 | this(payloadBuffer, reliability, RakPriority.NORMAL, 0); 38 | } 39 | 40 | public RakMessage(ByteBuf payloadBuffer, RakReliability reliability, RakPriority priority) { 41 | this(payloadBuffer, reliability, priority, 0); 42 | } 43 | 44 | public RakMessage(ByteBuf payloadBuffer, RakReliability reliability, RakPriority priority, int channel) { 45 | super(payloadBuffer); 46 | this.reliability = reliability; 47 | this.priority = priority; 48 | this.channel = channel; 49 | } 50 | 51 | /** 52 | * Returns the reliability of the message 53 | * 54 | * @return reliability 55 | */ 56 | public RakReliability reliability() { 57 | return reliability; 58 | } 59 | 60 | /** 61 | * Returns the priority of the message 62 | * 63 | * @return priority 64 | */ 65 | public RakPriority priority() { 66 | return priority; 67 | } 68 | 69 | /** 70 | * Returns the channel of the message 71 | * 72 | * @return channel 73 | */ 74 | public int channel() { 75 | return channel; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (this == o) { 81 | return true; 82 | } 83 | 84 | if (o == null || getClass() != o.getClass()) { 85 | return false; 86 | } 87 | 88 | RakMessage message = (RakMessage) o; 89 | 90 | if (reliability != message.reliability) { 91 | return false; 92 | } 93 | 94 | if (priority != message.priority) { 95 | return false; 96 | } 97 | 98 | if (channel != message.channel) { 99 | return false; 100 | } 101 | 102 | return content().equals(message.content()); 103 | } 104 | 105 | @Override 106 | public int hashCode() { 107 | int result = reliability.hashCode(); 108 | result = 31 * result + priority.hashCode(); 109 | result = 31 * result + channel; 110 | result = 31 * result + content().hashCode(); 111 | return result; 112 | } 113 | 114 | @Override 115 | public RakMessage copy() { 116 | return (RakMessage) super.copy(); 117 | } 118 | 119 | @Override 120 | public RakMessage duplicate() { 121 | return (RakMessage) super.duplicate(); 122 | } 123 | 124 | @Override 125 | public RakMessage retainedDuplicate() { 126 | return (RakMessage) super.retainedDuplicate(); 127 | } 128 | 129 | @Override 130 | public RakMessage replace(ByteBuf content) { 131 | return new RakMessage(content, reliability, priority, channel); 132 | } 133 | 134 | @Override 135 | public RakMessage retain() { 136 | return (RakMessage) super.retain(); 137 | } 138 | 139 | @Override 140 | public RakMessage touch() { 141 | return (RakMessage) super.touch(); 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | return "RakMessage{" + 147 | "reliability=" + reliability + 148 | ", priority=" + priority + 149 | ", channel=" + channel + 150 | "}"; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/packet/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.channel.raknet.packet; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/AdvancedChannelInboundHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet; 18 | 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelInboundHandlerAdapter; 21 | import io.netty.util.ReferenceCountUtil; 22 | import io.netty.util.internal.TypeParameterMatcher; 23 | 24 | public abstract class AdvancedChannelInboundHandler extends ChannelInboundHandlerAdapter { 25 | private final TypeParameterMatcher matcher; 26 | 27 | public AdvancedChannelInboundHandler() { 28 | this.matcher = TypeParameterMatcher.find(this, AdvancedChannelInboundHandler.class, "T"); 29 | } 30 | 31 | public AdvancedChannelInboundHandler(Class inboundMessageType) { 32 | this.matcher = TypeParameterMatcher.get(inboundMessageType); 33 | } 34 | 35 | protected boolean acceptInboundMessage(ChannelHandlerContext ctx, Object msg) throws Exception { 36 | return this.matcher.match(msg); 37 | } 38 | 39 | @Override 40 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 41 | boolean release = true; 42 | 43 | try { 44 | if (this.acceptInboundMessage(ctx, msg)) { 45 | this.channelRead0(ctx, (T) msg); 46 | } else { 47 | release = false; 48 | ctx.fireChannelRead(msg); 49 | } 50 | } finally { 51 | if (release) { 52 | ReferenceCountUtil.release(msg); 53 | } 54 | } 55 | } 56 | 57 | protected abstract void channelRead0(ChannelHandlerContext ctx, T msg) throws Exception; 58 | } 59 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/ProxyInboundRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet; 18 | 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelInboundHandler; 21 | import org.cloudburstmc.netty.channel.proxy.ProxyChannel; 22 | 23 | import java.nio.channels.ClosedChannelException; 24 | 25 | public class ProxyInboundRouter implements ChannelInboundHandler { 26 | 27 | public static final String NAME = "rak-proxy-inbound-router"; 28 | private final ProxyChannel proxiedChannel; 29 | 30 | public ProxyInboundRouter(ProxyChannel proxiedChannel) { 31 | this.proxiedChannel = proxiedChannel; 32 | } 33 | 34 | @Override 35 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 36 | this.proxiedChannel.pipeline().fireChannelRegistered(); 37 | } 38 | 39 | @Override 40 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 41 | this.proxiedChannel.pipeline().fireChannelUnregistered(); 42 | } 43 | 44 | @Override 45 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 46 | // Ignore 47 | } 48 | 49 | @Override 50 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 51 | // Ignore 52 | } 53 | 54 | @Override 55 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 56 | this.proxiedChannel.pipeline().fireChannelInactive(); 57 | } 58 | 59 | @Override 60 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 61 | // Ignore 62 | } 63 | 64 | @Override 65 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 66 | this.proxiedChannel.pipeline().fireChannelRead(msg); 67 | } 68 | 69 | @Override 70 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 71 | this.proxiedChannel.pipeline().fireChannelReadComplete(); 72 | } 73 | 74 | @Override 75 | public void userEventTriggered(ChannelHandlerContext ctx, Object msg) throws Exception { 76 | this.proxiedChannel.pipeline().fireUserEventTriggered(msg); 77 | } 78 | 79 | @Override 80 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 81 | this.proxiedChannel.pipeline().fireChannelWritabilityChanged(); 82 | } 83 | 84 | @Override 85 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception { 86 | if (!(throwable instanceof ClosedChannelException)) { 87 | this.proxiedChannel.pipeline().fireExceptionCaught(throwable); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/ProxyOutboundRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet; 18 | 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelOutboundHandler; 21 | import io.netty.channel.ChannelPromise; 22 | import org.cloudburstmc.netty.channel.proxy.ProxyChannel; 23 | 24 | import java.net.PortUnreachableException; 25 | import java.net.SocketAddress; 26 | 27 | public class ProxyOutboundRouter implements ChannelOutboundHandler { 28 | 29 | public static final String NAME = "rak-proxy-outbound-router"; 30 | private final ProxyChannel proxiedChannel; 31 | 32 | public ProxyOutboundRouter(ProxyChannel proxiedChannel) { 33 | this.proxiedChannel = proxiedChannel; 34 | } 35 | 36 | @Override 37 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 38 | // Ignore 39 | } 40 | 41 | @Override 42 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 43 | // Ignore 44 | } 45 | 46 | @Override 47 | public void bind(ChannelHandlerContext ctx, SocketAddress address, ChannelPromise promise) throws Exception { 48 | this.proxiedChannel.parent().bind(address, this.proxiedChannel.correctPromise(promise)); 49 | } 50 | 51 | @Override 52 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { 53 | this.proxiedChannel.parent().connect(remoteAddress, localAddress, this.proxiedChannel.correctPromise(promise)); 54 | } 55 | 56 | @Override 57 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 58 | this.proxiedChannel.onCloseTriggered(promise); 59 | } 60 | 61 | @Override 62 | public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 63 | this.proxiedChannel.parent().disconnect(this.proxiedChannel.correctPromise(promise)); 64 | } 65 | 66 | @Override 67 | public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 68 | this.proxiedChannel.parent().deregister(this.proxiedChannel.correctPromise(promise)); 69 | } 70 | 71 | @Override 72 | public void read(ChannelHandlerContext ctx) throws Exception { 73 | this.proxiedChannel.parent().read(); 74 | } 75 | 76 | @Override 77 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 78 | this.proxiedChannel.parent().write(msg, this.proxiedChannel.correctPromise(promise)); 79 | } 80 | 81 | @Override 82 | public void flush(ChannelHandlerContext ctx) throws Exception { 83 | this.proxiedChannel.parent().flush(); 84 | } 85 | 86 | @Override 87 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception { 88 | if (!(throwable instanceof PortUnreachableException)) { 89 | ctx.fireExceptionCaught(throwable); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.client; 18 | 19 | 20 | import io.netty.buffer.ByteBuf; 21 | import io.netty.channel.Channel; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.channel.ChannelPromise; 24 | import io.netty.channel.SimpleChannelInboundHandler; 25 | import org.cloudburstmc.netty.channel.raknet.RakChannel; 26 | import org.cloudburstmc.netty.channel.raknet.RakPriority; 27 | import org.cloudburstmc.netty.channel.raknet.RakReliability; 28 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; 29 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 30 | import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; 31 | import org.cloudburstmc.netty.util.RakUtils; 32 | 33 | import java.net.InetSocketAddress; 34 | 35 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 36 | 37 | public class RakClientOnlineInitialHandler extends SimpleChannelInboundHandler { 38 | public static final String NAME = "rak-client-online-initial-handler"; 39 | 40 | private final RakChannel rakChannel; 41 | private final ChannelPromise successPromise; 42 | 43 | public RakClientOnlineInitialHandler(RakChannel rakChannel, ChannelPromise promise) { 44 | this.rakChannel = rakChannel; 45 | this.successPromise = promise; 46 | } 47 | 48 | @Override 49 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 50 | this.sendConnectionRequest(ctx); 51 | } 52 | 53 | private void sendConnectionRequest(ChannelHandlerContext ctx) { 54 | long guid = this.rakChannel.config().getOption(RakChannelOption.RAK_GUID); 55 | 56 | ByteBuf buffer = ctx.alloc().ioBuffer(18); 57 | buffer.writeByte(ID_CONNECTION_REQUEST); 58 | buffer.writeLong(guid); 59 | buffer.writeLong(System.currentTimeMillis()); 60 | buffer.writeBoolean(false); 61 | ctx.writeAndFlush(new RakMessage(buffer, RakReliability.RELIABLE_ORDERED, RakPriority.IMMEDIATE)); 62 | } 63 | 64 | private void onSuccess(ChannelHandlerContext ctx) { 65 | // At this point connection is fully initialized. 66 | Channel channel = ctx.channel(); 67 | channel.pipeline().remove(RakClientOfflineHandler.NAME); 68 | channel.pipeline().remove(RakClientOnlineInitialHandler.NAME); 69 | this.successPromise.trySuccess(); 70 | } 71 | 72 | @Override 73 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket message) throws Exception { 74 | ByteBuf buf = message.getBuffer(); 75 | int packetId = buf.getUnsignedByte(buf.readerIndex()); 76 | 77 | switch (packetId) { 78 | case ID_CONNECTION_REQUEST_ACCEPTED: 79 | this.onConnectionRequestAccepted(ctx, buf); 80 | this.onSuccess(ctx); 81 | break; 82 | case ID_CONNECTION_REQUEST_FAILED: 83 | this.successPromise.tryFailure(new IllegalStateException("Connection denied")); 84 | break; 85 | default: 86 | ctx.fireChannelRead(message.retain()); 87 | break; 88 | } 89 | } 90 | 91 | private void onConnectionRequestAccepted(ChannelHandlerContext ctx, ByteBuf buf) { 92 | buf.skipBytes(1); 93 | 94 | boolean compatibilityMode = this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE); 95 | if (compatibilityMode) { 96 | RakUtils.skipAddress(buf); // Client address 97 | } else { 98 | RakUtils.readAddress(buf); // Client address 99 | } 100 | 101 | buf.readUnsignedShort(); // System index 102 | 103 | // Address + 2 * Long - Minimum amount of data 104 | int required = IPV4_MESSAGE_SIZE + 16; 105 | 106 | long pingTime = 0; 107 | while (buf.isReadable(required)) { 108 | if (compatibilityMode) { 109 | RakUtils.skipAddress(buf); 110 | } else { 111 | RakUtils.readAddress(buf); 112 | } 113 | } 114 | pingTime = buf.readLong(); 115 | buf.readLong(); 116 | 117 | ByteBuf buffer = ctx.alloc().ioBuffer(); 118 | buffer.writeByte(ID_NEW_INCOMING_CONNECTION); 119 | RakUtils.writeAddress(buffer, (InetSocketAddress) ctx.channel().remoteAddress()); 120 | for (int i = 0; i < this.rakChannel.config().getOption(RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES); i++) { 121 | RakUtils.writeAddress(buffer, LOCAL_ADDRESS); 122 | } 123 | buffer.writeLong(pingTime); 124 | buffer.writeLong(System.currentTimeMillis()); 125 | ctx.writeAndFlush(new RakMessage(buffer, RakReliability.RELIABLE_ORDERED, RakPriority.IMMEDIATE)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientProxyRouteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.client; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelDuplexHandler; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.ChannelPromise; 23 | import io.netty.channel.socket.DatagramPacket; 24 | import org.cloudburstmc.netty.channel.raknet.RakClientChannel; 25 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelMetrics; 26 | 27 | public class RakClientProxyRouteHandler extends ChannelDuplexHandler { 28 | public static final String NAME = "rak-client-proxy-route-handler"; 29 | 30 | private final RakClientChannel channel; 31 | 32 | public RakClientProxyRouteHandler(RakClientChannel channel) { 33 | this.channel = channel; 34 | } 35 | 36 | @Override 37 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 38 | if (!(msg instanceof DatagramPacket)) { 39 | ctx.fireChannelRead(msg); 40 | return; 41 | } 42 | 43 | DatagramPacket packet = (DatagramPacket) msg; 44 | RakChannelMetrics metrics = this.channel.config().getMetrics(); 45 | if (metrics != null) { 46 | metrics.bytesIn(packet.content().readableBytes()); 47 | } 48 | 49 | DatagramPacket datagram = packet.retain(); 50 | try { 51 | if (packet.sender() == null || packet.sender().equals(this.channel.remoteAddress())) { 52 | ctx.fireChannelRead(datagram.content()); 53 | } else { 54 | ctx.fireChannelRead(datagram); 55 | } 56 | } finally { 57 | datagram.release(); 58 | } 59 | } 60 | 61 | @Override 62 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 63 | boolean isDatagram = msg instanceof DatagramPacket; 64 | if (!isDatagram && !(msg instanceof ByteBuf)) { 65 | ctx.write(msg, promise); 66 | return; 67 | } 68 | 69 | DatagramPacket datagram = isDatagram ? (DatagramPacket) msg : new DatagramPacket((ByteBuf) msg, this.channel.remoteAddress()); 70 | RakChannelMetrics metrics = this.channel.config().getMetrics(); 71 | if (metrics != null) { 72 | metrics.bytesOut(datagram.content().readableBytes()); 73 | } 74 | 75 | ctx.write(datagram, promise); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientRouteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.client; 18 | 19 | import io.netty.channel.*; 20 | import io.netty.util.concurrent.PromiseCombiner; 21 | import org.cloudburstmc.netty.channel.raknet.RakClientChannel; 22 | import org.cloudburstmc.netty.handler.codec.raknet.common.UnconnectedPongDecoder; 23 | 24 | import java.net.InetSocketAddress; 25 | import java.net.SocketAddress; 26 | 27 | public class RakClientRouteHandler extends ChannelDuplexHandler { 28 | 29 | public static final String NAME = "rak-client-route-handler"; 30 | private final RakClientChannel channel; 31 | 32 | public RakClientRouteHandler(RakClientChannel channel) { 33 | this.channel = channel; 34 | } 35 | 36 | @Override 37 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { 38 | if (!(remoteAddress instanceof InetSocketAddress)) { 39 | promise.tryFailure(new IllegalArgumentException("Provided remote address must be InetSocketAddress")); 40 | return; 41 | } 42 | 43 | if (this.channel.parent().isActive()) { 44 | throw new IllegalStateException("Channel is already bound!"); 45 | } 46 | 47 | ChannelFuture parentFuture = this.channel.parent().connect(remoteAddress, localAddress); 48 | parentFuture.addListener(future -> { 49 | if (future.isSuccess()) { 50 | this.channel.rakPipeline().addAfter(UnconnectedPongDecoder.NAME, 51 | RakClientOfflineHandler.NAME, new RakClientOfflineHandler(channel, this.channel.getConnectPromise())); 52 | } 53 | }); 54 | 55 | PromiseCombiner combiner = new PromiseCombiner(this.channel.eventLoop()); 56 | combiner.add(parentFuture); 57 | combiner.add((ChannelFuture) this.channel.getConnectPromise()); 58 | combiner.finish(promise); 59 | } 60 | 61 | @Override 62 | public void read(ChannelHandlerContext ctx) throws Exception { 63 | // Ignore 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.handler.codec.raknet.client; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/ConnectedPingHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import org.cloudburstmc.netty.channel.raknet.RakPriority; 22 | import org.cloudburstmc.netty.channel.raknet.RakReliability; 23 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 24 | import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; 25 | import org.cloudburstmc.netty.handler.codec.raknet.AdvancedChannelInboundHandler; 26 | 27 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_CONNECTED_PING; 28 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_CONNECTED_PONG; 29 | 30 | public class ConnectedPingHandler extends AdvancedChannelInboundHandler { 31 | public static final String NAME = "rak-connected-ping-handler"; 32 | 33 | @Override 34 | protected boolean acceptInboundMessage(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | if (!super.acceptInboundMessage(ctx, msg)) { 36 | return false; 37 | } 38 | 39 | ByteBuf buf = ((EncapsulatedPacket) msg).getBuffer(); 40 | return buf.getUnsignedByte(buf.readerIndex()) == ID_CONNECTED_PING; 41 | } 42 | 43 | @Override 44 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket packet) throws Exception { 45 | ByteBuf buf = packet.getBuffer(); 46 | buf.readUnsignedByte(); // Packet ID 47 | long pingTime = buf.readLong(); 48 | 49 | ByteBuf replyBuffer = ctx.alloc().ioBuffer(17); 50 | replyBuffer.writeByte(ID_CONNECTED_PONG); 51 | replyBuffer.writeLong(pingTime); 52 | replyBuffer.writeLong(System.currentTimeMillis()); 53 | ctx.writeAndFlush(new RakMessage(replyBuffer, RakReliability.UNRELIABLE, RakPriority.IMMEDIATE)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/ConnectedPongHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import org.cloudburstmc.netty.channel.raknet.RakConstants; 22 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 23 | import org.cloudburstmc.netty.handler.codec.raknet.AdvancedChannelInboundHandler; 24 | 25 | public class ConnectedPongHandler extends AdvancedChannelInboundHandler { 26 | public static final String NAME = "rak-connected-pong-handler"; 27 | 28 | private final RakSessionCodec sessionCodec; 29 | 30 | public ConnectedPongHandler(RakSessionCodec sessionCodec) { 31 | this.sessionCodec = sessionCodec; 32 | } 33 | 34 | @Override 35 | protected boolean acceptInboundMessage(ChannelHandlerContext ctx, Object msg) throws Exception { 36 | if (!super.acceptInboundMessage(ctx, msg)) { 37 | return false; 38 | } 39 | 40 | ByteBuf buf = ((EncapsulatedPacket) msg).getBuffer(); 41 | return buf.getUnsignedByte(buf.readerIndex()) == RakConstants.ID_CONNECTED_PONG; 42 | } 43 | 44 | @Override 45 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket packet) throws Exception { 46 | ByteBuf buf = packet.getBuffer(); 47 | buf.readUnsignedByte(); // Packet ID 48 | long pingTime = buf.readLong(); 49 | this.sessionCodec.recalculatePongTime(pingTime); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/DisconnectNotificationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandler; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.util.internal.logging.InternalLogger; 23 | import io.netty.util.internal.logging.InternalLoggerFactory; 24 | import org.cloudburstmc.netty.channel.raknet.RakConstants; 25 | import org.cloudburstmc.netty.channel.raknet.RakDisconnectReason; 26 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 27 | import org.cloudburstmc.netty.handler.codec.raknet.AdvancedChannelInboundHandler; 28 | 29 | @ChannelHandler.Sharable 30 | public class DisconnectNotificationHandler extends AdvancedChannelInboundHandler { 31 | private static final InternalLogger log = InternalLoggerFactory.getInstance(DisconnectNotificationHandler.class); 32 | 33 | public static final DisconnectNotificationHandler INSTANCE = new DisconnectNotificationHandler(); 34 | public static final String NAME = "rak-disconnect-notification-handler"; 35 | 36 | @Override 37 | protected boolean acceptInboundMessage(ChannelHandlerContext ctx, Object msg) throws Exception { 38 | if (!super.acceptInboundMessage(ctx, msg)) { 39 | return false; 40 | } 41 | 42 | ByteBuf buf = ((EncapsulatedPacket) msg).getBuffer(); 43 | return buf.getUnsignedByte(buf.readerIndex()) == RakConstants.ID_DISCONNECTION_NOTIFICATION; 44 | } 45 | 46 | @Override 47 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket packet) throws Exception { 48 | ByteBuf buf = packet.getBuffer(); 49 | buf.readUnsignedByte(); // Packet ID 50 | if (log.isTraceEnabled()) { 51 | log.trace("RakNet Session ({} => {}) by remote peer!", ctx.channel().localAddress(), ctx.channel().remoteAddress()); 52 | } 53 | ctx.fireUserEventTriggered(RakDisconnectReason.CLOSED_BY_REMOTE_PEER); 54 | ctx.fireChannelInactive(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/EncapsulatedToMessageHandler.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.handler.codec.raknet.common; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 7 | 8 | @ChannelHandler.Sharable 9 | public class EncapsulatedToMessageHandler extends SimpleChannelInboundHandler { 10 | public static final String NAME = "encapsulated-to-message"; 11 | public static final EncapsulatedToMessageHandler INSTANCE = new EncapsulatedToMessageHandler(); 12 | 13 | @Override 14 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket packet) throws Exception { 15 | ctx.fireChannelRead(packet.toMessage().retain()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/RakAcknowledgeHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.SimpleChannelInboundHandler; 22 | import io.netty.util.internal.logging.InternalLogger; 23 | import io.netty.util.internal.logging.InternalLoggerFactory; 24 | import org.cloudburstmc.netty.channel.raknet.RakDisconnectReason; 25 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelMetrics; 26 | import org.cloudburstmc.netty.util.IntRange; 27 | 28 | import java.util.Queue; 29 | 30 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 31 | 32 | public class RakAcknowledgeHandler extends SimpleChannelInboundHandler { 33 | 34 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakAcknowledgeHandler.class); 35 | public static final String NAME = "rak-acknowledge-handler"; 36 | 37 | private final RakSessionCodec sessionCodec; 38 | 39 | public RakAcknowledgeHandler(RakSessionCodec sessionCodec) { 40 | this.sessionCodec = sessionCodec; 41 | } 42 | 43 | @Override 44 | public boolean acceptInboundMessage(Object msg) throws Exception { 45 | if (!super.acceptInboundMessage(msg)) { 46 | return false; 47 | } 48 | 49 | ByteBuf buffer = (ByteBuf) msg; 50 | byte potentialFlags = buffer.getByte(buffer.readerIndex()); 51 | if ((potentialFlags & FLAG_VALID) == 0) { 52 | return false; 53 | } 54 | 55 | return (potentialFlags & FLAG_ACK) != 0 || (potentialFlags & FLAG_NACK) != 0; 56 | } 57 | 58 | @Override 59 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { 60 | boolean nack = (buffer.readByte() & FLAG_NACK) != 0; 61 | int entriesCount = buffer.readUnsignedShort(); 62 | 63 | Queue queue = this.sessionCodec.getAcknowledgeQueue(nack); 64 | for (int i = 0; i < entriesCount; i++) { 65 | boolean singleton = buffer.readBoolean(); 66 | int start = buffer.readUnsignedMediumLE(); 67 | // We don't need the upper limit if it's a singleton 68 | int end = singleton ? start : buffer.readUnsignedMediumLE(); 69 | 70 | if (start <= end) { 71 | queue.offer(new IntRange(start, end)); 72 | continue; 73 | } 74 | 75 | if (log.isTraceEnabled()) { 76 | log.trace("{} sent an IntRange with a start value {} greater than an end value of {}", sessionCodec.getChannel().remoteAddress(), start, end); 77 | } 78 | this.sessionCodec.disconnect(RakDisconnectReason.BAD_PACKET); 79 | return; 80 | } 81 | 82 | RakChannelMetrics metrics = this.sessionCodec.getMetrics(); 83 | if (metrics != null) { 84 | if (nack) { 85 | metrics.nackIn(entriesCount); 86 | } else { 87 | metrics.ackIn(entriesCount); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/RakDatagramCodec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.CompositeByteBuf; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.handler.codec.MessageToMessageCodec; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 26 | import org.cloudburstmc.netty.channel.raknet.packet.RakDatagramPacket; 27 | 28 | import java.util.List; 29 | 30 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 31 | 32 | public class RakDatagramCodec extends MessageToMessageCodec { 33 | public static final String NAME = "rak-datagram-codec"; 34 | 35 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakDatagramCodec.class); 36 | 37 | public RakDatagramCodec() { 38 | } 39 | 40 | @Override 41 | protected void encode(ChannelHandlerContext ctx, RakDatagramPacket packet, List out) throws Exception { 42 | ByteBuf header = ctx.alloc().ioBuffer(4); 43 | header.writeByte(packet.getFlags()); 44 | header.writeMediumLE(packet.getSequenceIndex()); 45 | 46 | // Use a composite buffer so we don't have to do any memory copying. 47 | CompositeByteBuf buf = ctx.alloc().compositeBuffer((packet.getPackets().size() * 2) + 1); 48 | buf.addComponent(true, header); 49 | 50 | for (EncapsulatedPacket encapsulated : packet.getPackets()) { 51 | encapsulated.encode(buf); 52 | } 53 | out.add(buf); 54 | } 55 | 56 | @Override 57 | protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List list) throws Exception { 58 | byte potentialFlags = buffer.getByte(buffer.readerIndex()); 59 | if ((potentialFlags & FLAG_VALID) == 0) { 60 | // Not a RakNet datagram 61 | list.add(buffer.retain()); 62 | return; 63 | } 64 | 65 | if ((potentialFlags & FLAG_ACK) != 0 || (potentialFlags & FLAG_NACK) != 0) { 66 | // Do not handle Acknowledge packets here 67 | list.add(buffer.retain()); 68 | return; 69 | } 70 | 71 | RakDatagramPacket packet = RakDatagramPacket.newInstance(); 72 | try { 73 | packet.setFlags(buffer.readByte()); 74 | packet.setSequenceIndex(buffer.readUnsignedMediumLE()); 75 | while (buffer.isReadable()) { 76 | EncapsulatedPacket encapsulated = EncapsulatedPacket.newInstance(); 77 | try { 78 | encapsulated.decode(buffer); 79 | packet.getPackets().add(encapsulated.retain()); 80 | } catch (Throwable t) { 81 | log.error("Error decoding encapsulated packet", t); // TODO: this is just temporary for debugging 82 | throw t; 83 | } finally { 84 | encapsulated.release(); 85 | } 86 | } 87 | list.add(packet.retain()); 88 | } finally { 89 | packet.release(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/RakUnhandledMessagesQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.SimpleChannelInboundHandler; 21 | import io.netty.util.ReferenceCountUtil; 22 | import io.netty.util.concurrent.ScheduledFuture; 23 | import io.netty.util.internal.PlatformDependent; 24 | import org.cloudburstmc.netty.channel.raknet.RakChannel; 25 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 26 | 27 | import java.util.Queue; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | public class RakUnhandledMessagesQueue extends SimpleChannelInboundHandler { 31 | public static final String NAME = "rak-unhandled-messages-queue"; 32 | 33 | private final RakChannel channel; 34 | private final Queue messages = PlatformDependent.newMpscQueue(); 35 | private ScheduledFuture future; 36 | 37 | public RakUnhandledMessagesQueue(RakChannel channel) { 38 | this.channel = channel; 39 | } 40 | 41 | @Override 42 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 43 | this.future = ctx.channel().eventLoop().scheduleAtFixedRate(() -> this.trySendMessages(ctx), 44 | 0, 50, TimeUnit.MILLISECONDS); 45 | } 46 | 47 | @Override 48 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 49 | if (this.future != null) { 50 | this.future.cancel(false); 51 | this.future = null; 52 | } 53 | 54 | EncapsulatedPacket message; 55 | while ((message = this.messages.poll()) != null) { 56 | ReferenceCountUtil.release(message); 57 | } 58 | } 59 | 60 | private void trySendMessages(ChannelHandlerContext ctx) { 61 | if (!this.channel.isActive()) { 62 | return; 63 | } 64 | 65 | EncapsulatedPacket message; 66 | while ((message = this.messages.poll()) != null) { 67 | ctx.fireChannelRead(message); 68 | } 69 | 70 | ctx.pipeline().remove(this); 71 | } 72 | 73 | @Override 74 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket msg) throws Exception { 75 | if (!this.channel.isActive()) { 76 | this.messages.offer(msg.retain()); 77 | return; 78 | } 79 | 80 | this.trySendMessages(ctx); 81 | ctx.fireChannelRead(msg.retain()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/UnconnectedPingEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.ChannelOutboundHandlerAdapter; 22 | import io.netty.channel.ChannelPromise; 23 | import io.netty.channel.socket.DatagramPacket; 24 | import org.cloudburstmc.netty.channel.raknet.RakClientChannel; 25 | import org.cloudburstmc.netty.channel.raknet.RakPing; 26 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; 27 | 28 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_UNCONNECTED_PING; 29 | 30 | public class UnconnectedPingEncoder extends ChannelOutboundHandlerAdapter { 31 | public static final String NAME = "rak-unconnected-ping-encoder"; 32 | 33 | private final RakClientChannel channel; 34 | 35 | public UnconnectedPingEncoder(RakClientChannel channel) { 36 | this.channel = channel; 37 | } 38 | 39 | @Override 40 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 41 | if (!(msg instanceof RakPing)) { 42 | ctx.write(msg, promise); 43 | return; 44 | } 45 | 46 | RakPing ping = (RakPing) msg; 47 | ByteBuf magicBuf = this.channel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); 48 | long guid = this.channel.config().getOption(RakChannelOption.RAK_GUID); 49 | 50 | ByteBuf pingBuffer = ctx.alloc().ioBuffer(magicBuf.readableBytes() + 17); 51 | pingBuffer.writeByte(ID_UNCONNECTED_PING); 52 | pingBuffer.writeLong(ping.getPingTime()); 53 | pingBuffer.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes()); 54 | pingBuffer.writeLong(guid); 55 | ctx.write(new DatagramPacket(pingBuffer, ping.getSender()), promise); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/UnconnectedPongDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufUtil; 21 | import io.netty.buffer.Unpooled; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.channel.socket.DatagramPacket; 24 | import org.cloudburstmc.netty.channel.raknet.RakClientChannel; 25 | import org.cloudburstmc.netty.channel.raknet.RakPong; 26 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; 27 | import org.cloudburstmc.netty.handler.codec.raknet.AdvancedChannelInboundHandler; 28 | 29 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_UNCONNECTED_PONG; 30 | 31 | public class UnconnectedPongDecoder extends AdvancedChannelInboundHandler { 32 | public static final String NAME = "rak-unconnected-pong-deencoder"; 33 | 34 | private final RakClientChannel channel; 35 | 36 | public UnconnectedPongDecoder(RakClientChannel channel) { 37 | this.channel = channel; 38 | } 39 | 40 | @Override 41 | protected boolean acceptInboundMessage(ChannelHandlerContext ctx, Object msg) throws Exception { 42 | if (!super.acceptInboundMessage(ctx, msg)) { 43 | return false; 44 | } 45 | 46 | DatagramPacket packet = (DatagramPacket) msg; 47 | ByteBuf buf = packet.content(); 48 | return buf.isReadable() && buf.getUnsignedByte(buf.readerIndex()) == ID_UNCONNECTED_PONG; 49 | } 50 | 51 | @Override 52 | protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception { 53 | ByteBuf buf = packet.content(); 54 | buf.readUnsignedByte(); // Packet ID 55 | 56 | long pingTime = buf.readLong(); 57 | long guid = buf.readLong(); 58 | 59 | ByteBuf magicBuf = this.channel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); 60 | if (!buf.isReadable(magicBuf.readableBytes()) || !ByteBufUtil.equals(buf.readSlice(magicBuf.readableBytes()), magicBuf)) { 61 | // Magic does not match 62 | return; 63 | } 64 | 65 | ByteBuf pongData = Unpooled.EMPTY_BUFFER; 66 | if (buf.isReadable(2)) { // Length 67 | pongData = buf.readRetainedSlice(buf.readUnsignedShort()); 68 | } 69 | ctx.fireChannelRead(new RakPong(pingTime, guid, pongData, packet.sender())); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/UnconnectedPongEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.common; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandler.Sharable; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.ChannelOutboundHandlerAdapter; 23 | import io.netty.channel.ChannelPromise; 24 | import io.netty.channel.socket.DatagramPacket; 25 | import org.cloudburstmc.netty.channel.raknet.RakPong; 26 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; 27 | 28 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_UNCONNECTED_PONG; 29 | 30 | @Sharable 31 | public class UnconnectedPongEncoder extends ChannelOutboundHandlerAdapter { 32 | public static final UnconnectedPongEncoder INSTANCE = new UnconnectedPongEncoder(); 33 | public static final String NAME = "rak-unconnected-pong-encoder"; 34 | 35 | private UnconnectedPongEncoder() { 36 | } 37 | 38 | @Override 39 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 40 | if (!(msg instanceof RakPong)) { 41 | ctx.write(msg, promise); 42 | return; 43 | } 44 | 45 | RakPong pong = (RakPong) msg; 46 | ByteBuf magicBuf = ctx.channel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); 47 | long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID); 48 | 49 | ByteBuf pongData = pong.getPongData(); 50 | ByteBuf pongBuffer = ctx.alloc().ioBuffer(magicBuf.readableBytes() + 19 + pongData.readableBytes()); 51 | pongBuffer.writeByte(ID_UNCONNECTED_PONG); 52 | pongBuffer.writeLong(pong.getPingTime()); 53 | pongBuffer.writeLong(guid); 54 | pongBuffer.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes()); 55 | pongBuffer.writeShort(pongData.readableBytes()); 56 | pongBuffer.writeBytes(pongData, pongData.readerIndex(), pongData.readableBytes()); 57 | ctx.write(new DatagramPacket(pongBuffer, pong.getSender()), promise); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/common/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.handler.codec.raknet.common; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.handler.codec.raknet; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/RakChildDatagramHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.server; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.*; 21 | import io.netty.channel.socket.DatagramPacket; 22 | import org.cloudburstmc.netty.channel.raknet.RakChildChannel; 23 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelMetrics; 24 | 25 | import java.nio.channels.ClosedChannelException; 26 | 27 | public class RakChildDatagramHandler extends ChannelOutboundHandlerAdapter { 28 | 29 | public static final String NAME = "rak-child-datagram-handler"; 30 | private final RakChildChannel channel; 31 | private volatile boolean canFlush = false; 32 | 33 | public RakChildDatagramHandler(RakChildChannel channel) { 34 | this.channel = channel; 35 | } 36 | 37 | @Override 38 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 39 | boolean isDatagram = msg instanceof DatagramPacket; 40 | if (!isDatagram && !(msg instanceof ByteBuf)) { 41 | ctx.write(msg, promise); 42 | return; 43 | } 44 | 45 | this.canFlush = true; 46 | promise.trySuccess(); 47 | DatagramPacket datagram = isDatagram ? (DatagramPacket) msg : 48 | new DatagramPacket((ByteBuf) msg, this.channel.remoteAddress(), this.channel.localAddress()); 49 | 50 | RakChannelMetrics metrics = this.channel.config().getMetrics(); 51 | if (metrics != null) { 52 | metrics.bytesOut(datagram.content().readableBytes()); 53 | } 54 | 55 | Channel parent = this.channel.parent().parent(); 56 | 57 | parent.write(datagram).addListener((ChannelFuture future) -> { 58 | if (!future.isSuccess() && !(future.cause() instanceof ClosedChannelException)) { 59 | this.channel.pipeline().fireExceptionCaught(future.cause()); 60 | this.channel.close(); 61 | } 62 | }); 63 | } 64 | 65 | @Override 66 | public void flush(ChannelHandlerContext ctx) throws Exception { 67 | if (this.canFlush) { 68 | this.canFlush = false; 69 | ctx.flush(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/RakServerOnlineInitialHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.server; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandler.Sharable; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.SimpleChannelInboundHandler; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | import org.cloudburstmc.netty.channel.raknet.RakChildChannel; 26 | import org.cloudburstmc.netty.channel.raknet.RakDisconnectReason; 27 | import org.cloudburstmc.netty.channel.raknet.RakPriority; 28 | import org.cloudburstmc.netty.channel.raknet.RakReliability; 29 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; 30 | import org.cloudburstmc.netty.channel.raknet.config.RakServerChannelConfig; 31 | import org.cloudburstmc.netty.channel.raknet.config.RakServerMetrics; 32 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 33 | import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; 34 | import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; 35 | import org.cloudburstmc.netty.util.RakUtils; 36 | 37 | import java.net.Inet6Address; 38 | import java.net.InetSocketAddress; 39 | 40 | import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; 41 | 42 | @Sharable 43 | public class RakServerOnlineInitialHandler extends SimpleChannelInboundHandler { 44 | public static final String NAME = "rak-server-online-initial-handler"; 45 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakServerOnlineInitialHandler.class); 46 | 47 | private final RakChildChannel channel; 48 | 49 | public RakServerOnlineInitialHandler(RakChildChannel channel) { 50 | this.channel = channel; 51 | } 52 | 53 | @Override 54 | protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket message) throws Exception { 55 | ByteBuf buf = message.getBuffer(); 56 | int packetId = buf.getUnsignedByte(buf.readerIndex()); 57 | 58 | RakServerMetrics metrics = this.channel.parent().config().getOption(RakChannelOption.RAK_SERVER_METRICS); 59 | 60 | switch (packetId) { 61 | case ID_CONNECTION_REQUEST: 62 | if (metrics != null) metrics.connectionInitPacket(this.channel.remoteAddress(), ID_CONNECTION_REQUEST); 63 | this.onConnectionRequest(ctx, buf); 64 | break; 65 | case ID_NEW_INCOMING_CONNECTION: 66 | if (metrics != null) metrics.connectionInitPacket(this.channel.remoteAddress(), ID_NEW_INCOMING_CONNECTION); 67 | 68 | buf.skipBytes(1); 69 | // We have connected and no longer need this handler 70 | ctx.pipeline().remove(this); 71 | channel.eventLoop().execute(() -> { 72 | channel.setActive(true); // make sure this is set on same thread 73 | channel.pipeline().fireChannelActive(); 74 | }); 75 | break; 76 | default: 77 | ctx.fireChannelRead(message.retain()); 78 | break; 79 | } 80 | } 81 | 82 | private void onConnectionRequest(ChannelHandlerContext ctx, ByteBuf buffer) { 83 | buffer.skipBytes(1); 84 | long guid = this.channel.config().getGuid(); 85 | long serverGuid = buffer.readLong(); 86 | long timestamp = buffer.readLong(); 87 | boolean security = buffer.readBoolean(); 88 | 89 | if (serverGuid != guid || security) { 90 | this.sendConnectionRequestFailed(ctx, guid); 91 | } else { 92 | this.sendConnectionRequestAccepted(ctx, timestamp); 93 | } 94 | } 95 | 96 | private void sendConnectionRequestAccepted(ChannelHandlerContext ctx, long time) { 97 | InetSocketAddress address = this.channel.remoteAddress(); 98 | boolean ipv6 = address.getAddress() instanceof Inet6Address; 99 | ByteBuf outBuf = ctx.alloc().ioBuffer(ipv6 ? 628 : 166); 100 | 101 | outBuf.writeByte(ID_CONNECTION_REQUEST_ACCEPTED); 102 | RakUtils.writeAddress(outBuf, address); 103 | outBuf.writeShort(0); // System index 104 | for (InetSocketAddress socketAddress : ipv6 ? LOCAL_IP_ADDRESSES_V6 : LOCAL_IP_ADDRESSES_V4) { 105 | RakUtils.writeAddress(outBuf, socketAddress); 106 | } 107 | outBuf.writeLong(time); 108 | outBuf.writeLong(System.currentTimeMillis()); 109 | 110 | ctx.writeAndFlush(new RakMessage(outBuf, RakReliability.UNRELIABLE, RakPriority.IMMEDIATE)); 111 | } 112 | 113 | private void sendConnectionRequestFailed(ChannelHandlerContext ctx, long guid) { 114 | ByteBuf magicBuf = ((RakServerChannelConfig) ctx.channel().config()).getUnconnectedMagic(); 115 | int length = 9 + magicBuf.readableBytes(); 116 | 117 | ByteBuf reply = ctx.alloc().ioBuffer(length); 118 | reply.writeByte(ID_CONNECTION_REQUEST_FAILED); 119 | reply.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes()); 120 | reply.writeLong(guid); 121 | 122 | sendRaw(ctx, reply); 123 | ctx.fireUserEventTriggered(RakDisconnectReason.CONNECTION_REQUEST_FAILED).close(); 124 | } 125 | 126 | private void sendRaw(ChannelHandlerContext ctx, ByteBuf buf) { 127 | ctx.pipeline().context(RakSessionCodec.NAME).writeAndFlush(buf); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/RakServerRateLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.server; 18 | 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.SimpleChannelInboundHandler; 21 | import io.netty.channel.socket.DatagramPacket; 22 | import io.netty.util.concurrent.ScheduledFuture; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | import org.cloudburstmc.netty.channel.raknet.RakServerChannel; 26 | import org.cloudburstmc.netty.channel.raknet.config.RakServerMetrics; 27 | 28 | import java.net.InetAddress; 29 | import java.util.*; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | import java.util.concurrent.TimeUnit; 32 | import java.util.concurrent.atomic.AtomicInteger; 33 | import java.util.concurrent.atomic.AtomicLong; 34 | 35 | public class RakServerRateLimiter extends SimpleChannelInboundHandler { 36 | public static final String NAME = "rak-server-rate-limiter"; 37 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakServerRateLimiter.class); 38 | 39 | private final RakServerChannel channel; 40 | 41 | private final ConcurrentHashMap rateLimitMap = new ConcurrentHashMap<>(); 42 | private final Map blockedConnections = new ConcurrentHashMap<>(); 43 | 44 | private final Collection exceptions = Collections.newSetFromMap(new ConcurrentHashMap<>()); 45 | 46 | private final AtomicLong globalCounter = new AtomicLong(0); 47 | 48 | private ScheduledFuture tickFuture; 49 | private ScheduledFuture blockedTickFuture; 50 | 51 | public RakServerRateLimiter(RakServerChannel channel) { 52 | this.channel = channel; 53 | } 54 | 55 | @Override 56 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 57 | this.tickFuture = ctx.channel().eventLoop().scheduleAtFixedRate(this::onRakTick, 10, 10, TimeUnit.MILLISECONDS); 58 | this.blockedTickFuture = ctx.channel().eventLoop().scheduleAtFixedRate(this::onBlockedTick, 100, 100, TimeUnit.MILLISECONDS); 59 | } 60 | 61 | @Override 62 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 63 | this.tickFuture.cancel(false); 64 | this.blockedTickFuture.cancel(true); 65 | this.rateLimitMap.clear(); 66 | } 67 | 68 | protected void onRakTick() { 69 | this.rateLimitMap.clear(); 70 | this.globalCounter.set(0); 71 | } 72 | 73 | protected void onBlockedTick() { 74 | long currTime = System.currentTimeMillis(); 75 | 76 | RakServerMetrics metrics = this.channel.config().getMetrics(); 77 | 78 | Iterator> iterator = this.blockedConnections.entrySet().iterator(); 79 | while (iterator.hasNext()) { 80 | Map.Entry entry = iterator.next(); 81 | if (entry.getValue() != 0 && currTime > entry.getValue()) { 82 | iterator.remove(); 83 | log.info("Unblocked address {}", entry.getKey()); 84 | if (metrics != null) { 85 | metrics.addressUnblocked(entry.getKey()); 86 | } 87 | } 88 | } 89 | } 90 | 91 | public boolean blockAddress(InetAddress address, long time, TimeUnit unit) { 92 | if (this.exceptions.contains(address)) { 93 | return false; 94 | } 95 | 96 | long millis = unit.toMillis(time); 97 | this.blockedConnections.put(address, System.currentTimeMillis() + millis); 98 | 99 | if (this.channel.config().getMetrics() != null) { 100 | this.channel.config().getMetrics().addressBlocked(address); 101 | } 102 | return true; 103 | } 104 | 105 | public void unblockAddress(InetAddress address) { 106 | if (this.blockedConnections.remove(address) == null) { 107 | return; 108 | } 109 | 110 | log.info("Unblocked address {}", address); 111 | 112 | if (this.channel.config().getMetrics() != null) { 113 | this.channel.config().getMetrics().addressUnblocked(address); 114 | } 115 | } 116 | 117 | public boolean isAddressBlocked(InetAddress address) { 118 | return this.blockedConnections.containsKey(address); 119 | } 120 | 121 | public void addException(InetAddress address) { 122 | this.exceptions.add(address); 123 | } 124 | 125 | public void removeException(InetAddress address) { 126 | this.exceptions.remove(address); 127 | } 128 | 129 | public Collection getExceptions() { 130 | return Collections.unmodifiableCollection(this.exceptions); 131 | } 132 | 133 | protected int getAddressMaxPacketCount(InetAddress address) { 134 | return this.channel.config().getPacketLimit(); 135 | } 136 | 137 | @Override 138 | protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagram) throws Exception { 139 | if (this.globalCounter.incrementAndGet() > this.channel.config().getGlobalPacketLimit()) { 140 | if (log.isTraceEnabled()) { 141 | log.trace("[{}] Dropped incoming packet because global packet limit was reached: {}", datagram.sender(), this.globalCounter.get()); 142 | } 143 | return; 144 | } 145 | 146 | InetAddress address = datagram.sender().getAddress(); 147 | if (this.blockedConnections.containsKey(address)) { 148 | return; 149 | } 150 | 151 | AtomicInteger counter = this.rateLimitMap.computeIfAbsent(address, a -> new AtomicInteger()); 152 | if (counter.incrementAndGet() > this.getAddressMaxPacketCount(address) && 153 | this.blockAddress(address, 10, TimeUnit.SECONDS)) { 154 | log.warn("[{}] Blocked because packet limit was reached", address); 155 | } else { 156 | ctx.fireChannelRead(datagram.retain()); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/RakServerRouteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.server; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelDuplexHandler; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.socket.DatagramPacket; 23 | import org.cloudburstmc.netty.channel.raknet.RakChildChannel; 24 | import org.cloudburstmc.netty.channel.raknet.RakServerChannel; 25 | import org.cloudburstmc.netty.channel.raknet.config.RakChannelMetrics; 26 | 27 | public class RakServerRouteHandler extends ChannelDuplexHandler { 28 | 29 | public static final String NAME = "rak-server-route-handler"; 30 | private final RakServerChannel parent; 31 | 32 | public RakServerRouteHandler(RakServerChannel parent) { 33 | this.parent = parent; 34 | } 35 | 36 | @Override 37 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 38 | if (!(msg instanceof DatagramPacket)) { 39 | ctx.fireChannelRead(msg); 40 | return; 41 | } 42 | DatagramPacket packet = (DatagramPacket) msg; 43 | 44 | try { 45 | RakChildChannel channel = this.parent.getChildChannel(packet.sender()); 46 | if (channel == null) { 47 | // Pass DatagramPacket which holds remote address and payload. 48 | ctx.fireChannelRead(packet.retain()); 49 | return; 50 | } 51 | 52 | RakChannelMetrics metrics = channel.config().getMetrics(); 53 | if (metrics != null) { 54 | metrics.bytesIn(packet.content().readableBytes()); 55 | } 56 | 57 | // In this case remote address is already known from ChannelHandlerContext 58 | // so we can pass only payload. 59 | ByteBuf buffer = packet.content().retain(); 60 | channel.rakPipeline().fireChannelRead(buffer).fireChannelReadComplete(); 61 | } finally { 62 | packet.release(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/RakServerTailHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.handler.codec.raknet.server; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelHandler.Sharable; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.ChannelInboundHandlerAdapter; 23 | import io.netty.util.ReferenceCountUtil; 24 | import io.netty.util.internal.logging.InternalLogger; 25 | import io.netty.util.internal.logging.InternalLoggerFactory; 26 | 27 | @Sharable 28 | public class RakServerTailHandler extends ChannelInboundHandlerAdapter { 29 | 30 | public static final String NAME = "rak-server-tail-handler"; 31 | 32 | public static final RakServerTailHandler INSTANCE = new RakServerTailHandler(); 33 | 34 | private static final InternalLogger log = InternalLoggerFactory.getInstance(RakServerTailHandler.class); 35 | 36 | @Override 37 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 38 | if (msg instanceof Channel) { 39 | super.channelRead(ctx, msg); 40 | } else { 41 | ReferenceCountUtil.release(msg); 42 | log.trace("Received unexpected message in server channel: {}", msg); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/server/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.handler.codec.raknet.server; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/BitQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.util; 18 | 19 | public class BitQueue { 20 | 21 | private long[] queue; 22 | private int head; 23 | private int tail; 24 | 25 | public BitQueue() { 26 | this(0); 27 | } 28 | 29 | public BitQueue(int capacity) { 30 | capacity = RakUtils.powerOfTwoCeiling(capacity); 31 | if (capacity <= 0) { 32 | capacity = 64; 33 | } 34 | 35 | this.queue = new long[((capacity + 63) >> 6)]; 36 | this.head = 0; 37 | this.tail = 0; 38 | } 39 | 40 | public void add(boolean bit) { 41 | if (((this.head + 1) & ((this.queue.length << 6) - 1)) == this.tail) { 42 | this.resize(this.queue.length << 7); 43 | } 44 | 45 | int by = this.head >> 6; 46 | long bi = 1L << (this.head & 63); 47 | this.queue[by] ^= ((bit ? 0xFFFFFFFFFFFFFFFFL : 0) ^ this.queue[by]) & bi; 48 | this.head = (this.head + 1) & ((this.queue.length << 6) - 1); 49 | } 50 | 51 | private void resize(int capacity) { 52 | long[] newQueue = new long[(capacity + 63) >> 6]; 53 | int size = this.size(); 54 | 55 | if ((this.tail & 63) == 0) { 56 | if (this.head > this.tail) { 57 | int srcPos = this.tail >> 6; 58 | int length = (this.head - this.tail + 63) >> 6; 59 | System.arraycopy(this.queue, srcPos, newQueue, 0, length); 60 | } else if (this.head < this.tail) { 61 | int length = this.tail >> 6; 62 | int adjustedPos = ((this.queue.length << 6) - this.tail + 63) >> 6; 63 | System.arraycopy(this.queue, length, newQueue, 0, adjustedPos); 64 | length = (this.head + 63) >> 6; 65 | System.arraycopy(this.queue, 0, newQueue, adjustedPos, length); 66 | } 67 | 68 | this.tail = 0; 69 | this.head = size; 70 | } else { 71 | int tailBits = (this.tail & 63); 72 | int tailIdx = this.tail >> 6; 73 | int by2 = (tailIdx + 1) & (this.queue.length - 1); 74 | long mask; 75 | long bit1; 76 | long bit2; 77 | 78 | int cursor = 0; 79 | while (cursor < size) { 80 | mask = ((1L << tailBits) - 1); 81 | bit1 = ((this.queue[tailIdx] & ~mask) >>> tailBits); 82 | bit2 = (this.queue[by2] << (64 - tailBits)); 83 | newQueue[cursor >> 6] = (bit1 | bit2); 84 | 85 | cursor += 64; 86 | tailIdx = (tailIdx + 1) & (this.queue.length - 1); 87 | by2 = (by2 + 1) & (this.queue.length - 1); 88 | } 89 | 90 | this.tail = 0; 91 | this.head = size; 92 | } 93 | 94 | this.queue = newQueue; 95 | } 96 | 97 | public int size() { 98 | if (this.head > this.tail) { 99 | return (this.head - this.tail); 100 | } else if (this.head < this.tail) { 101 | return ((this.queue.length << 6) - (this.tail - this.head)); 102 | } else { 103 | return 0; 104 | } 105 | } 106 | 107 | public void set(int n, boolean bit) { 108 | if (n >= this.size() || n < 0) { 109 | return; 110 | } 111 | 112 | int idx = (this.tail + n) & ((this.queue.length << 6) - 1); 113 | int arrIdx = idx >> 6; 114 | long mask = 1L << (idx & 63); 115 | this.queue[arrIdx] ^= ((bit ? 0xFF : 0x00) ^ this.queue[arrIdx]) & mask; 116 | } 117 | 118 | public boolean get(int n) { 119 | if (n >= this.size() || n < 0) { 120 | return false; 121 | } 122 | 123 | int idx = (this.tail + n) & ((this.queue.length << 6) - 1); 124 | int arrIdx = idx >> 6; 125 | long mask = 1L << (idx & 63); 126 | return (this.queue[arrIdx] & mask) != 0; 127 | } 128 | 129 | public boolean isEmpty() { 130 | return (this.head == this.tail); 131 | } 132 | 133 | public boolean peek() { 134 | if (this.head == this.tail) { 135 | return false; 136 | } 137 | 138 | int arrIdx = this.tail >> 6; 139 | long mask = 1L << ((this.tail) & 63); 140 | return (this.queue[arrIdx] & mask) != 0; 141 | } 142 | 143 | public boolean poll() { 144 | if (this.head == this.tail) { 145 | return false; 146 | } 147 | 148 | int arrIdx = this.tail >> 6; 149 | long mask = 1L << ((this.tail) & 63); 150 | this.tail = (this.tail + 1) & ((this.queue.length << 6) - 1); 151 | return (this.queue[arrIdx] & mask) != 0; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/IntRange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.util; 18 | 19 | public class IntRange { 20 | public int start; 21 | public int end; 22 | 23 | public IntRange(int num) { 24 | this(num, num); 25 | } 26 | 27 | public IntRange(int start, int end) { 28 | this.start = start; 29 | this.end = end; 30 | } 31 | } -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/IpDontFragmentProvider.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.util; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelOption; 5 | import io.netty.channel.socket.nio.NioChannelOption; 6 | import io.netty.channel.unix.IntegerUnixChannelOption; 7 | 8 | import java.lang.reflect.Field; 9 | import java.net.SocketOption; 10 | 11 | public class IpDontFragmentProvider { 12 | private static final ChannelOption IP_DONT_FRAGMENT_OPTION; 13 | private static final Object IP_DONT_FRAGMENT_TRUE_VALUE; 14 | private static final Object IP_DONT_FRAGMENT_FALSE_VALUE; 15 | 16 | static { 17 | ChannelOption ipDontFragmentOption = null; 18 | Object ipDontFragmentTrueValue = null; 19 | Object ipDontFragmentFalseValue = null; 20 | 21 | setterBlock: { 22 | // Windows and Linux Compatible (Java 19+) 23 | try { 24 | Class c = Class.forName("jdk.net.ExtendedSocketOptions"); 25 | Field f = c.getField("IP_DONTFRAGMENT"); 26 | 27 | ipDontFragmentOption = NioChannelOption.of((SocketOption) f.get(null)); 28 | ipDontFragmentTrueValue = true; 29 | ipDontFragmentFalseValue = false; 30 | break setterBlock; 31 | } catch (ClassNotFoundException | NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { 32 | 33 | } 34 | 35 | // Unix Compatible (Java 8+) 36 | ipDontFragmentOption = new IntegerUnixChannelOption("IP_DONTFRAG", 0 /* IPPROTO_IP */, 10 /* IP_MTU_DISCOVER */); 37 | ipDontFragmentTrueValue = 2 /* IP_PMTUDISC_DO */; 38 | ipDontFragmentFalseValue = 0 /* IP_PMTUDISC_DONT */; 39 | break setterBlock; 40 | } 41 | 42 | IP_DONT_FRAGMENT_OPTION = ipDontFragmentOption; 43 | IP_DONT_FRAGMENT_TRUE_VALUE = ipDontFragmentTrueValue; 44 | IP_DONT_FRAGMENT_FALSE_VALUE = ipDontFragmentFalseValue; 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | public static boolean trySet(Channel channel, boolean value) { 49 | if (IP_DONT_FRAGMENT_OPTION == null) return false; 50 | boolean success = channel.config().setOption((ChannelOption) IP_DONT_FRAGMENT_OPTION, (T) (value ? IP_DONT_FRAGMENT_TRUE_VALUE : IP_DONT_FRAGMENT_FALSE_VALUE)); 51 | return success ? value : !value; 52 | } 53 | } -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/RoundRobinArray.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.util; 18 | 19 | import io.netty.util.ReferenceCountUtil; 20 | 21 | import java.util.Arrays; 22 | import java.util.Collection; 23 | import java.util.Iterator; 24 | import java.util.NoSuchElementException; 25 | 26 | public class RoundRobinArray implements Collection { 27 | 28 | private final Object[] elements; 29 | private final int mask; 30 | 31 | public RoundRobinArray(int fixedCapacity) { 32 | fixedCapacity = RakUtils.powerOfTwoCeiling(fixedCapacity); 33 | 34 | this.elements = new Object[fixedCapacity]; 35 | this.mask = fixedCapacity - 1; 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | public E get(int index) { 40 | return (E) this.elements[index & this.mask]; 41 | } 42 | 43 | public void set(int index, E value) { 44 | int idx = index & this.mask; 45 | Object element = this.elements[idx]; 46 | this.elements[idx] = value; 47 | // Make sure to release any reference counted objects that get overwritten. 48 | ReferenceCountUtil.release(element); 49 | } 50 | 51 | public void remove(int index, E expected) { 52 | int idx = index & this.mask; 53 | Object element = this.elements[idx]; 54 | if (element == expected) { 55 | this.elements[idx] = null; 56 | // Make sure to release the element on removal. 57 | ReferenceCountUtil.release(element); 58 | } 59 | } 60 | 61 | @Override 62 | public int size() { 63 | throw new UnsupportedOperationException(); 64 | } 65 | 66 | @Override 67 | public boolean isEmpty() { 68 | throw new UnsupportedOperationException(); 69 | } 70 | 71 | @Override 72 | public boolean contains(Object o) { 73 | throw new UnsupportedOperationException(); 74 | } 75 | 76 | @Override 77 | public Iterator iterator() { 78 | return new Itr(); 79 | } 80 | 81 | @Override 82 | public Object[] toArray() { 83 | return Arrays.copyOf(this.elements, this.elements.length); 84 | } 85 | 86 | @SuppressWarnings("unchecked") 87 | @Override 88 | public T[] toArray(T[] a) { 89 | if (a.length < this.elements.length) 90 | // Make a new array of a's runtime type, but my contents: 91 | return (T[]) Arrays.copyOf(this.elements, this.elements.length, a.getClass()); 92 | System.arraycopy(this.elements, 0, a, 0, this.elements.length); 93 | if (a.length > this.elements.length) 94 | a[this.elements.length] = null; 95 | return a; 96 | } 97 | 98 | @Override 99 | public boolean add(E e) { 100 | throw new UnsupportedOperationException(); 101 | } 102 | 103 | @Override 104 | public boolean remove(Object o) { 105 | throw new UnsupportedOperationException(); 106 | } 107 | 108 | @Override 109 | public boolean containsAll(Collection c) { 110 | throw new UnsupportedOperationException(); 111 | } 112 | 113 | @Override 114 | public boolean addAll(Collection c) { 115 | throw new UnsupportedOperationException(); 116 | } 117 | 118 | @Override 119 | public boolean removeAll(Collection c) { 120 | throw new UnsupportedOperationException(); 121 | } 122 | 123 | @Override 124 | public boolean retainAll(Collection c) { 125 | throw new UnsupportedOperationException(); 126 | } 127 | 128 | @Override 129 | public void clear() { 130 | Arrays.fill(this.elements, null); 131 | } 132 | 133 | private class Itr implements Iterator { 134 | int cursor; // index of next element to return 135 | int lastRet = -1; // index of last element returned; -1 if no such 136 | 137 | Itr() { 138 | } 139 | 140 | public boolean hasNext() { 141 | return cursor != elements.length; 142 | } 143 | 144 | @SuppressWarnings("unchecked") 145 | public E next() { 146 | int i = cursor; 147 | if (i >= elements.length) { 148 | throw new NoSuchElementException(); 149 | } 150 | cursor = i + 1; 151 | return (E) elements[lastRet = i]; 152 | } 153 | 154 | public void remove() { 155 | if (lastRet < 0) { 156 | throw new IllegalStateException(); 157 | } 158 | 159 | Object object = elements[lastRet]; 160 | elements[lastRet] = null; 161 | ReferenceCountUtil.release(object); 162 | cursor = lastRet; 163 | lastRet = -1; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/RoundRobinIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.util; 18 | 19 | import java.util.Collection; 20 | import java.util.Iterator; 21 | 22 | public class RoundRobinIterator implements Iterator { 23 | private final Collection collection; 24 | private Iterator iterator; 25 | 26 | public RoundRobinIterator(Collection collection) { 27 | this.collection = collection; 28 | this.iterator = this.collection.iterator(); 29 | } 30 | 31 | @Override 32 | public synchronized boolean hasNext() { 33 | return !collection.isEmpty(); 34 | } 35 | 36 | @Override 37 | public synchronized E next() { 38 | if (!this.iterator.hasNext()) { 39 | this.iterator = this.collection.iterator(); 40 | } 41 | return this.iterator.next(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/SecureAlgorithmProvider.java: -------------------------------------------------------------------------------- 1 | package org.cloudburstmc.netty.util; 2 | 3 | import java.security.Provider; 4 | import java.security.SecureRandom; 5 | import java.security.Security; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.stream.Stream; 9 | 10 | public class SecureAlgorithmProvider { 11 | private static final String SECURITY_ALGORITHM; 12 | 13 | static { 14 | // SecureRandom algorithms in order of most preferred to least preferred. 15 | final List preferredAlgorithms = Arrays.asList( 16 | "SHA1PRNG", 17 | "NativePRNGNonBlocking", 18 | "Windows-PRNG", 19 | "NativePRNG", 20 | "PKCS11", 21 | "DRBG", 22 | "NativePRNGBlocking" 23 | ); 24 | 25 | SECURITY_ALGORITHM = Stream.of(Security.getProviders()) 26 | .flatMap(provider -> provider.getServices().stream()) 27 | .filter(service -> "SecureRandom".equals(service.getType())) 28 | .map(Provider.Service::getAlgorithm) 29 | .filter(preferredAlgorithms::contains) 30 | .min((s1, s2) -> Integer.compare(preferredAlgorithms.indexOf(s1), preferredAlgorithms.indexOf(s2))) 31 | .orElse(new SecureRandom().getAlgorithm()); 32 | } 33 | 34 | public static String getSecurityAlgorithm() { 35 | return SECURITY_ALGORITHM; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/SplitPacketHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty.util; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.netty.util.AbstractReferenceCounted; 22 | import io.netty.util.IllegalReferenceCountException; 23 | import io.netty.util.ReferenceCountUtil; 24 | import io.netty.util.ReferenceCounted; 25 | import org.cloudburstmc.netty.channel.raknet.packet.EncapsulatedPacket; 26 | 27 | import java.util.Objects; 28 | 29 | public class SplitPacketHelper extends AbstractReferenceCounted { 30 | private final EncapsulatedPacket[] packets; 31 | private final long created = System.currentTimeMillis(); 32 | 33 | public SplitPacketHelper(long expectedLength) { 34 | if (expectedLength < 2) { 35 | throw new IllegalArgumentException("expectedLength must be greater than 1"); 36 | } 37 | this.packets = new EncapsulatedPacket[(int) expectedLength]; 38 | } 39 | 40 | public EncapsulatedPacket add(EncapsulatedPacket packet, ByteBufAllocator alloc) { 41 | Objects.requireNonNull(packet, "packet cannot be null"); 42 | if (!packet.isSplit()) throw new IllegalArgumentException("Packet is not split"); 43 | if (this.refCnt() <= 0) throw new IllegalReferenceCountException(this.refCnt()); 44 | if (packet.getPartIndex() < 0 || packet.getPartIndex() >= this.packets.length) { 45 | throw new IllegalArgumentException(String.format("Split packet part index out of range. Got %s, expected 0-%s", 46 | packet.getPartIndex(), this.packets.length - 1)); 47 | } 48 | 49 | int partIndex = packet.getPartIndex(); 50 | if (this.packets[partIndex] != null) { 51 | // Duplicate 52 | return null; 53 | } 54 | // Retain the packet so it can be reassembled later. 55 | this.packets[partIndex] = packet.retain(); 56 | 57 | int sz = 0; 58 | for (EncapsulatedPacket netPacket : this.packets) { 59 | if (netPacket == null) { 60 | return null; 61 | } 62 | sz += netPacket.getBuffer().readableBytes(); 63 | } 64 | 65 | // We can't use a composite buffer as the native code will choke on it 66 | ByteBuf reassembled = alloc.ioBuffer(sz); 67 | for (EncapsulatedPacket netPacket : this.packets) { 68 | ByteBuf buf = netPacket.getBuffer(); 69 | reassembled.writeBytes(buf, buf.readerIndex(), buf.readableBytes()); 70 | } 71 | 72 | return packet.fromSplit(reassembled); 73 | } 74 | 75 | public boolean expired() { 76 | // If we're waiting on a split packet for more than 30 seconds, the client on the other end is either severely 77 | // lagging, or has died. 78 | if (this.refCnt() <= 0) throw new IllegalReferenceCountException(this.refCnt()); 79 | return System.currentTimeMillis() - created >= 30000; 80 | } 81 | 82 | @Override 83 | protected void deallocate() { 84 | for (EncapsulatedPacket packet : this.packets) { 85 | ReferenceCountUtil.release(packet); 86 | } 87 | } 88 | 89 | @Override 90 | public ReferenceCounted touch(Object hint) { 91 | throw new UnsupportedOperationException(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /transport-raknet/src/main/java/org/cloudburstmc/netty/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * 19 | */ 20 | package org.cloudburstmc.netty.util; 21 | -------------------------------------------------------------------------------- /transport-raknet/src/test/java/org/cloudburstmc/netty/BitQueueTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CloudburstMC 3 | * 4 | * CloudburstMC licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package org.cloudburstmc.netty; 18 | 19 | import org.cloudburstmc.netty.util.BitQueue; 20 | import org.junit.jupiter.api.Assertions; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.ArrayDeque; 24 | import java.util.Queue; 25 | import java.util.concurrent.ThreadLocalRandom; 26 | 27 | public class BitQueueTests { 28 | 29 | @Test 30 | public void testQueue() { 31 | Queue bits = new ArrayDeque<>(); 32 | BitQueue queue = new BitQueue(); 33 | 34 | for (int i = 0; i < 256; i++) { 35 | boolean value = ThreadLocalRandom.current().nextBoolean(); 36 | queue.add(value); 37 | bits.add(value); 38 | } 39 | 40 | while (!queue.isEmpty() && !bits.isEmpty()) { 41 | boolean expected = bits.poll(); 42 | boolean actual = queue.poll(); 43 | Assertions.assertEquals(expected, actual, "Expected %s but got %s"); 44 | } 45 | Assertions.assertTrue(queue.isEmpty() && bits.isEmpty(), "Queue is not empty"); 46 | } 47 | } 48 | --------------------------------------------------------------------------------