From 8f0d33667f50297c428d13aae8e01f537c3e8021 Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:58:31 -0400 Subject: [PATCH 01/10] Add wildcard forced hosts support (#1587) --- .../connection/client/ConnectedPlayer.java | 15 +++++++-- .../util/ServerListPingHandler.java | 13 +++++++- .../proxy/util/AddressUtil.java | 33 +++++++++++++++++++ .../connection/client/ForcedHostsTest.java | 17 ++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 8d39fcf9f6..fc00a70d3d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -99,6 +99,7 @@ import com.velocitypowered.proxy.tablist.KeyedVelocityTabList; import com.velocitypowered.proxy.tablist.VelocityTabList; import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; +import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import com.velocitypowered.proxy.util.DurationUtils; import com.velocitypowered.proxy.util.TranslatableMapper; @@ -111,6 +112,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -890,8 +892,17 @@ private Optional getNextServerToTry(@Nullable RegisteredServer String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString) .orElse("") .toLowerCase(Locale.ROOT); - serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(virtualHostStr, - Collections.emptyList()); + serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( + virtualHostStr, + server.getConfiguration() + .getForcedHosts() + .entrySet() + .stream() + .filter(entry -> AddressUtil.isHostMatchingPattern(entry.getKey(), virtualHostStr)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(Collections.emptyList()) + ); } if (serversToTry.isEmpty()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 5ccb2b1105..0bb09399f2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -28,11 +28,13 @@ import com.velocitypowered.proxy.config.PingPassthroughMode; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import com.velocitypowered.proxy.util.AddressUtil; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -175,7 +177,16 @@ public CompletableFuture getInitialPing(VelocityInboundConnection co .map(str -> str.toLowerCase(Locale.ROOT)) .orElse(""); List serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( - virtualHostStr, server.getConfiguration().getAttemptConnectionOrder()); + virtualHostStr, + server.getConfiguration() + .getForcedHosts() + .entrySet() + .stream() + .filter(entry -> AddressUtil.isHostMatchingPattern(entry.getKey(), virtualHostStr)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(server.getConfiguration().getAttemptConnectionOrder()) + ); return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index 42c5228e89..f20f0e759c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -74,4 +74,37 @@ public static InetSocketAddress parseAndResolveAddress(String ip) { int port = uri.getPort() == -1 ? DEFAULT_MINECRAFT_PORT : uri.getPort(); return new InetSocketAddress(uri.getHost(), port); } + + /** + * Tests whether a host matches a pattern whose labels may be the + * wildcard {@code "*"}. Each {@code "*"} matches exactly one label; all other + * labels match case-insensitively. + * + * @param pattern the pattern to match against, for example, {@code *.soulrealms.net} + * @param host the virtual host to test, for example, {@code play.soulrealms.net} + * @return true if the host matches the pattern, false otherwise + */ + public static boolean isHostMatchingPattern(String pattern, String host) { + if (host == null || pattern == null) { + return false; + } + + String[] patternDomains = pattern.split("\\."); + String[] strDomains = host.split("\\."); + + if (patternDomains.length != strDomains.length) { + return false; + } + + for (int i = 0; patternDomains.length > i; i++) { + String patternDomain = patternDomains[i]; + String strDomain = strDomains[i]; + + if (!patternDomain.equals("*") && !strDomain.equalsIgnoreCase(patternDomain)) { + return false; + } + } + + return true; + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java new file mode 100644 index 0000000000..93b5e33b14 --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java @@ -0,0 +1,17 @@ +package com.velocitypowered.proxy.connection.client; + +import com.velocitypowered.proxy.util.AddressUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ForcedHostsTest { + @Test + void testIsHostMatchingPattern() { + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "play.miscpvp.org")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "yt.miscpvp.org")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "ip.miscpvp.org")); + + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "miscpvp.org")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("miscpvp.minehunt.gg", "ip.miscpvp.org")); + } +} From cdbf7b5e7e42a00ba932c6dd4ae6fb12628b1b97 Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:10:05 -0400 Subject: [PATCH 02/10] Add additional test cases --- .../proxy/connection/client/ForcedHostsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java index 93b5e33b14..8f1ee2d450 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java @@ -10,7 +10,8 @@ void testIsHostMatchingPattern() { Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "play.miscpvp.org")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "yt.miscpvp.org")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "ip.miscpvp.org")); - + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("test.*.miscpvp.org", "test.example.miscpvp.org")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "test.example.miscpvp.org")); Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "miscpvp.org")); Assertions.assertFalse(AddressUtil.isHostMatchingPattern("miscpvp.minehunt.gg", "ip.miscpvp.org")); } From b777c79525e56136195830cde91ba3412d648e8e Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:30:25 -0400 Subject: [PATCH 03/10] Reformat test to pass checkstyle --- .../connection/client/ForcedHostsTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java index 8f1ee2d450..52a08590de 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java @@ -5,14 +5,14 @@ import org.junit.jupiter.api.Test; class ForcedHostsTest { - @Test - void testIsHostMatchingPattern() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "play.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "yt.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "ip.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("test.*.miscpvp.org", "test.example.miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "test.example.miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("miscpvp.minehunt.gg", "ip.miscpvp.org")); - } + @Test + void testIsHostMatchingPattern() { + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "play.miscpvp.org")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "yt.miscpvp.org")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "ip.miscpvp.org")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("test.*.miscpvp.org", "test.example.miscpvp.org")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "test.example.miscpvp.org")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "miscpvp.org")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("miscpvp.minehunt.gg", "ip.miscpvp.org")); + } } From 415d12791a052bd9b520c48f3817f0bbe5a67862 Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:51:41 -0400 Subject: [PATCH 04/10] Add missing GPL header --- .../connection/client/ForcedHostsTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java index 52a08590de..097fa3d0dd 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2018-2021 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.connection.client; import com.velocitypowered.proxy.util.AddressUtil; From 70efa5546b215288968bf644d3961e97183efe46 Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:11:43 -0400 Subject: [PATCH 05/10] Use fictious domains, add more test cases, and update GPL year to 2026 --- .../connection/client/ForcedHostsTest.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java index 097fa3d0dd..cb1f22aec2 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Velocity Contributors + * Copyright (C) 2018-2026 Velocity Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,12 +24,35 @@ class ForcedHostsTest { @Test void testIsHostMatchingPattern() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "play.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "yt.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "ip.miscpvp.org")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("test.*.miscpvp.org", "test.example.miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "test.example.miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.miscpvp.org", "miscpvp.org")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("miscpvp.minehunt.gg", "ip.miscpvp.org")); + // One wildcard, should match + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "play.example.com")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "a.example.com")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("b.*.example.com", "b.a.example.com")); + + // Different number of labels should not match + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "a.b.example.com")); + + // Wildcards should not match apex domains + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "example.com")); + + // Exact matches should match + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("example.com", "example.com")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("play.example.com", "play.example.com")); + + // Different domains shouldn't match + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("otherdomain.com", "example.com")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("a.otherdomain.com", "a.example.com")); + + // Malformed patterns and hosts + Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, "example.com")); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern("example.com", null)); + Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, null)); + + // Case insensitivity + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("Example.COM", "example.com")); + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.Example.com", "play.EXAMPLE.com")); + + // Multiple wildcards + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.*.example.com", "a.b.example.com")); } } From 268f942192842dbd16584b12cf554844e57494fb Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:13:05 -0400 Subject: [PATCH 06/10] Split tests into separate methods --- ...Test.java => HostPatternMatchingTest.java} | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) rename proxy/src/test/java/com/velocitypowered/proxy/connection/client/{ForcedHostsTest.java => HostPatternMatchingTest.java} (84%) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java similarity index 84% rename from proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java rename to proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java index cb1f22aec2..63f9aad434 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/ForcedHostsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java @@ -21,38 +21,51 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -class ForcedHostsTest { +class HostPatternMatchingTest { @Test - void testIsHostMatchingPattern() { - // One wildcard, should match + void testOneWildcardMatches() { Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "play.example.com")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "a.example.com")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("b.*.example.com", "b.a.example.com")); + } + + @Test + void testMultipleWildcardsMatch() { + Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.*.example.com", "a.b.example.com")); + } - // Different number of labels should not match + @Test + void testDifferentNumberOfLabelsDoNotMatch() { Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "a.b.example.com")); + } - // Wildcards should not match apex domains + @Test + void testWildcardsDoNotMatchApexDomain() { Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "example.com")); + } - // Exact matches should match + @Test + void testExactMatchesMatch() { Assertions.assertTrue(AddressUtil.isHostMatchingPattern("example.com", "example.com")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("play.example.com", "play.example.com")); + } - // Different domains shouldn't match + @Test + void testDifferentDomainsDoNotMatch() { Assertions.assertFalse(AddressUtil.isHostMatchingPattern("otherdomain.com", "example.com")); Assertions.assertFalse(AddressUtil.isHostMatchingPattern("a.otherdomain.com", "a.example.com")); + } - // Malformed patterns and hosts + @Test + void testMalformedArgumentsDoNotMatch() { Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, "example.com")); Assertions.assertFalse(AddressUtil.isHostMatchingPattern("example.com", null)); Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, null)); + } - // Case insensitivity + @Test + void testCaseInsensitivityMatches() { Assertions.assertTrue(AddressUtil.isHostMatchingPattern("Example.COM", "example.com")); Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.Example.com", "play.EXAMPLE.com")); - - // Multiple wildcards - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.*.example.com", "a.b.example.com")); } } From 9093623de69bfc1f69ba70061fae244aaefbb28b Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:15:20 -0400 Subject: [PATCH 07/10] Update javadoc, add @Nullable, and flip comparison for `isHostMatchingPattern` --- .../com/velocitypowered/proxy/util/AddressUtil.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index f20f0e759c..560be8c967 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -19,6 +19,8 @@ import com.google.common.base.Preconditions; import com.google.common.net.InetAddresses; + +import javax.annotation.Nullable; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; @@ -80,11 +82,11 @@ public static InetSocketAddress parseAndResolveAddress(String ip) { * wildcard {@code "*"}. Each {@code "*"} matches exactly one label; all other * labels match case-insensitively. * - * @param pattern the pattern to match against, for example, {@code *.soulrealms.net} - * @param host the virtual host to test, for example, {@code play.soulrealms.net} + * @param pattern the pattern to match against, for example, {@code *.example.com} + * @param host the virtual host to test, for example, {@code play.example.com} * @return true if the host matches the pattern, false otherwise */ - public static boolean isHostMatchingPattern(String pattern, String host) { + public static boolean isHostMatchingPattern(@Nullable String pattern, @Nullable String host) { if (host == null || pattern == null) { return false; } @@ -96,7 +98,7 @@ public static boolean isHostMatchingPattern(String pattern, String host) { return false; } - for (int i = 0; patternDomains.length > i; i++) { + for (int i = 0; i < patternDomains.length; i++) { String patternDomain = patternDomains[i]; String strDomain = strDomains[i]; From 36fba4a936505de1256066d84ddc436fc2d4c4a5 Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:30:33 -0400 Subject: [PATCH 08/10] Move tests to AddressUtilTest in correct package, import isHostMatchingPattern statically, import JUnit methods statically to match other tests --- .../client/HostPatternMatchingTest.java | 71 ------------------ .../proxy/protocol/util/AddressUtilTest.java | 73 +++++++++++++++++++ 2 files changed, 73 insertions(+), 71 deletions(-) delete mode 100644 proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/protocol/util/AddressUtilTest.java diff --git a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java b/proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java deleted file mode 100644 index 63f9aad434..0000000000 --- a/proxy/src/test/java/com/velocitypowered/proxy/connection/client/HostPatternMatchingTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2018-2026 Velocity Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.velocitypowered.proxy.connection.client; - -import com.velocitypowered.proxy.util.AddressUtil; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class HostPatternMatchingTest { - @Test - void testOneWildcardMatches() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "play.example.com")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.example.com", "a.example.com")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("b.*.example.com", "b.a.example.com")); - } - - @Test - void testMultipleWildcardsMatch() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.*.example.com", "a.b.example.com")); - } - - @Test - void testDifferentNumberOfLabelsDoNotMatch() { - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "a.b.example.com")); - } - - @Test - void testWildcardsDoNotMatchApexDomain() { - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("*.example.com", "example.com")); - } - - @Test - void testExactMatchesMatch() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("example.com", "example.com")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("play.example.com", "play.example.com")); - } - - @Test - void testDifferentDomainsDoNotMatch() { - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("otherdomain.com", "example.com")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("a.otherdomain.com", "a.example.com")); - } - - @Test - void testMalformedArgumentsDoNotMatch() { - Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, "example.com")); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern("example.com", null)); - Assertions.assertFalse(AddressUtil.isHostMatchingPattern(null, null)); - } - - @Test - void testCaseInsensitivityMatches() { - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("Example.COM", "example.com")); - Assertions.assertTrue(AddressUtil.isHostMatchingPattern("*.Example.com", "play.EXAMPLE.com")); - } -} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/util/AddressUtilTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/util/AddressUtilTest.java new file mode 100644 index 0000000000..0ddee64fbd --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/util/AddressUtilTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018-2026 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.util; + +import static com.velocitypowered.proxy.util.AddressUtil.isHostMatchingPattern; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class AddressUtilTest { + @Test + void testOneWildcardMatches() { + assertTrue(isHostMatchingPattern("*.example.com", "play.example.com")); + assertTrue(isHostMatchingPattern("*.example.com", "a.example.com")); + assertTrue(isHostMatchingPattern("b.*.example.com", "b.a.example.com")); + } + + @Test + void testMultipleWildcardsMatch() { + assertTrue(isHostMatchingPattern("*.*.example.com", "a.b.example.com")); + } + + @Test + void testDifferentNumberOfLabelsDoNotMatch() { + assertFalse(isHostMatchingPattern("*.example.com", "a.b.example.com")); + } + + @Test + void testWildcardsDoNotMatchApexDomain() { + assertFalse(isHostMatchingPattern("*.example.com", "example.com")); + } + + @Test + void testExactMatchesMatch() { + assertTrue(isHostMatchingPattern("example.com", "example.com")); + assertTrue(isHostMatchingPattern("play.example.com", "play.example.com")); + } + + @Test + void testDifferentDomainsDoNotMatch() { + assertFalse(isHostMatchingPattern("otherdomain.com", "example.com")); + assertFalse(isHostMatchingPattern("a.otherdomain.com", "a.example.com")); + } + + @Test + void testMalformedArgumentsDoNotMatch() { + assertFalse(isHostMatchingPattern(null, "example.com")); + assertFalse(isHostMatchingPattern("example.com", null)); + assertFalse(isHostMatchingPattern(null, null)); + } + + @Test + void testCaseInsensitivityMatches() { + assertTrue(isHostMatchingPattern("Example.COM", "example.com")); + assertTrue(isHostMatchingPattern("*.Example.com", "play.EXAMPLE.com")); + } +} From 54d6816e17d0c352024368f60479f6cfb5845c2b Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:59:51 -0400 Subject: [PATCH 09/10] Extract server resolution logic to separate method and optimize readability --- .../connection/client/ConnectedPlayer.java | 13 +------ .../util/ServerListPingHandler.java | 14 +------ .../proxy/util/AddressUtil.java | 39 ++++++++++++++++++- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index fc00a70d3d..fb840b0d3d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -112,7 +112,6 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -892,17 +891,7 @@ private Optional getNextServerToTry(@Nullable RegisteredServer String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString) .orElse("") .toLowerCase(Locale.ROOT); - serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( - virtualHostStr, - server.getConfiguration() - .getForcedHosts() - .entrySet() - .stream() - .filter(entry -> AddressUtil.isHostMatchingPattern(entry.getKey(), virtualHostStr)) - .map(Map.Entry::getValue) - .findFirst() - .orElse(Collections.emptyList()) - ); + serversToTry = AddressUtil.resolveForcedHostServers(server, virtualHostStr).orElseGet(Collections::emptyList); } if (serversToTry.isEmpty()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 0bb09399f2..231a6670e8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -34,7 +34,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -176,17 +175,8 @@ public CompletableFuture getInitialPing(VelocityInboundConnection co String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString) .map(str -> str.toLowerCase(Locale.ROOT)) .orElse(""); - List serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( - virtualHostStr, - server.getConfiguration() - .getForcedHosts() - .entrySet() - .stream() - .filter(entry -> AddressUtil.isHostMatchingPattern(entry.getKey(), virtualHostStr)) - .map(Map.Entry::getValue) - .findFirst() - .orElse(server.getConfiguration().getAttemptConnectionOrder()) - ); + List serversToTry = AddressUtil.resolveForcedHostServers(server, virtualHostStr) + .orElseGet(() -> server.getConfiguration().getAttemptConnectionOrder()); return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index 560be8c967..02744fbcbb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -19,11 +19,14 @@ import com.google.common.base.Preconditions; import com.google.common.net.InetAddresses; - -import javax.annotation.Nullable; +import com.velocitypowered.proxy.VelocityServer; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; /** * Utilities to parse addresses. @@ -109,4 +112,36 @@ public static boolean isHostMatchingPattern(@Nullable String pattern, @Nullable return true; } + + /** + * Resolves the list of servers configured for a given virtual host via forced hosts. An exact + * match on the virtual host is preferred. Then, if none exist, the configured forced host patterns are + * checked in turn and the first matching pattern's servers are returned. + * + * @param server the proxy server providing the forced host configuration + * @param virtualHostStr the virtual host the client connected with + * @return the servers for the matching forced host, or {@link Optional#empty()} if none match + */ + public static Optional> resolveForcedHostServers(VelocityServer server, String virtualHostStr) { + Map> forcedHosts = server.getConfiguration().getForcedHosts(); + + // Check for exact match + List exactMatch = forcedHosts.get(virtualHostStr); + + if (exactMatch != null) { + return Optional.of(exactMatch); + } + + // Check for pattern match + for (Map.Entry> entry : forcedHosts.entrySet()) { + String virtualHostPattern = entry.getKey(); + + if (AddressUtil.isHostMatchingPattern(virtualHostPattern, virtualHostStr)) { + return Optional.of(entry.getValue()); + } + } + + // No match + return Optional.empty(); + } } From 845de169e15cc5925ac504e09510fa0d189175ed Mon Sep 17 00:00:00 2001 From: Lincoln Maxwell <60242131+lllincoln@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:04:37 -0400 Subject: [PATCH 10/10] Use method reference in lambda --- .../proxy/connection/util/ServerListPingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 231a6670e8..7d5a3eaf1d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -176,7 +176,7 @@ public CompletableFuture getInitialPing(VelocityInboundConnection co .map(str -> str.toLowerCase(Locale.ROOT)) .orElse(""); List serversToTry = AddressUtil.resolveForcedHostServers(server, virtualHostStr) - .orElseGet(() -> server.getConfiguration().getAttemptConnectionOrder()); + .orElseGet(server.getConfiguration()::getAttemptConnectionOrder); return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr); } }