diff --git a/README.md b/README.md index f747fbd6..b2649d28 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ put *config.json* file into the unpacked folder before running server >> `serverName`: the Server Name Indication field in the SSL handshake. If left blank, it will be set to `server.host` +>> `verifyHostname`: whether to verify SSL hostname, default is `true` + ## Features ### Transport diff --git a/pom.xml b/pom.xml index 4fba1b03..511966a1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 urban-spork urban-spork - 2.4.4 + 2.4.5 pom urban-spork-common @@ -14,7 +14,7 @@ UTF-8 - 2.4.4 + 2.4.5 3.2.5 3.4.1 3.13.0 diff --git a/urban-spork-client-gui/pom.xml b/urban-spork-client-gui/pom.xml index 8689dba3..d4baa8f1 100644 --- a/urban-spork-client-gui/pom.xml +++ b/urban-spork-client-gui/pom.xml @@ -3,7 +3,7 @@ urban-spork urban-spork - 2.4.4 + 2.4.5 urban-spork-client-gui urban-spork-client-gui diff --git a/urban-spork-client/pom.xml b/urban-spork-client/pom.xml index 40151321..29173db9 100644 --- a/urban-spork-client/pom.xml +++ b/urban-spork-client/pom.xml @@ -3,7 +3,7 @@ urban-spork urban-spork - 2.4.4 + 2.4.5 urban-spork-client diff --git a/urban-spork-client/src/com/urbanspork/client/Client.java b/urban-spork-client/src/com/urbanspork/client/Client.java index 729c5f8c..8f864077 100644 --- a/urban-spork-client/src/com/urbanspork/client/Client.java +++ b/urban-spork-client/src/com/urbanspork/client/Client.java @@ -1,7 +1,6 @@ package com.urbanspork.client; import com.urbanspork.client.shadowsocks.ClientUdpRelayHandler; -import com.urbanspork.client.vmess.ClientUdpOverTcpHandler; import com.urbanspork.common.codec.socks.DatagramPacketDecoder; import com.urbanspork.common.codec.socks.DatagramPacketEncoder; import com.urbanspork.common.config.ClientConfig; @@ -26,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; @@ -41,7 +41,7 @@ public static void main(String[] args) { public static void launch(ClientConfig config, CompletableFuture promise) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); - GlobalChannelTrafficShapingHandler trafficShapingHandler = new GlobalChannelTrafficShapingHandler(bossGroup); + GlobalChannelTrafficShapingHandler trafficShapingHandler = new GlobalChannelTrafficShapingHandler(workerGroup); ServerConfig current = config.getCurrent(); current.setTrafficShapingHandler(trafficShapingHandler); try { @@ -81,7 +81,7 @@ private static DatagramChannel launchUdp(EventLoopGroup bossGroup, EventLoopGrou ServerConfig current = config.getCurrent(); ChannelHandler udpTransportHandler; if (Protocol.vmess == current.getProtocol()) { - udpTransportHandler = new ClientUdpOverTcpHandler(current, workerGroup); + udpTransportHandler = new com.urbanspork.client.vmess.ClientUdpOverTcpHandler(current, workerGroup); } else if (Protocol.trojan == current.getProtocol()) { udpTransportHandler = new com.urbanspork.client.trojan.ClientUdpOverTcpHandler(current, workerGroup); } else { @@ -102,7 +102,8 @@ protected void initChannel(Channel ch) { .bind(InetAddress.getLoopbackAddress(), config.getPort()).sync().channel(); } - public record Instance(ServerSocketChannel tcp, DatagramChannel udp, TrafficCounter traffic) { + public record Instance(ServerSocketChannel tcp, DatagramChannel udp, TrafficCounter traffic) implements Closeable { + @Override public void close() { traffic.stop(); tcp.close().awaitUninterruptibly(); diff --git a/urban-spork-client/src/com/urbanspork/client/ClientSocksInitializer.java b/urban-spork-client/src/com/urbanspork/client/ClientSocksInitializer.java index ee3745e0..a7eaabf4 100644 --- a/urban-spork-client/src/com/urbanspork/client/ClientSocksInitializer.java +++ b/urban-spork-client/src/com/urbanspork/client/ClientSocksInitializer.java @@ -11,7 +11,9 @@ import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; import java.io.File; public class ClientSocksInitializer extends ChannelInitializer { @@ -34,6 +36,7 @@ protected void initChannel(NioSocketChannel channel) { public static SslHandler buildSslHandler(Channel ch, ServerConfig config) throws SSLException { String serverName = config.getHost(); SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); + boolean verifyHostname = true; if (config.getSsl() != null) { SslSetting ssl = config.getSsl(); if (ssl.getCertificateFile() != null) { @@ -42,8 +45,16 @@ public static SslHandler buildSslHandler(Channel ch, ServerConfig config) throws if (ssl.getServerName() != null) { serverName = ssl.getServerName(); // override } + verifyHostname = ssl.isVerifyHostname(); } SslContext sslContext = sslContextBuilder.build(); - return sslContext.newHandler(ch.alloc(), serverName, config.getPort()); + SslHandler sslHandler = sslContext.newHandler(ch.alloc(), serverName, config.getPort()); + if (verifyHostname) { + SSLEngine sslEngine = sslHandler.engine(); + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(sslParameters); + } + return sslHandler; } } diff --git a/urban-spork-client/src/com/urbanspork/client/shadowsocks/ClientUdpRelayHandler.java b/urban-spork-client/src/com/urbanspork/client/shadowsocks/ClientUdpRelayHandler.java index ed156ea8..8819d47f 100644 --- a/urban-spork-client/src/com/urbanspork/client/shadowsocks/ClientUdpRelayHandler.java +++ b/urban-spork-client/src/com/urbanspork/client/shadowsocks/ClientUdpRelayHandler.java @@ -25,11 +25,13 @@ public class ClientUdpRelayHandler extends AbstractClientUdpRelayHandlerclient->sender new ExceptionHandler(config) ); @@ -58,6 +60,12 @@ protected void initChannel(Channel ch) { .syncUninterruptibly().channel(); } + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + super.handlerRemoved(ctx); + codec.handlerRemoved(ctx); + } + private static class InboundHandler extends SimpleChannelInboundHandler { private final Channel channel; diff --git a/urban-spork-common/pom.xml b/urban-spork-common/pom.xml index 1b7fb7f0..5651e9c9 100644 --- a/urban-spork-common/pom.xml +++ b/urban-spork-common/pom.xml @@ -4,7 +4,7 @@ urban-spork urban-spork - 2.4.4 + 2.4.5 urban-spork-common diff --git a/urban-spork-common/src/com/urbanspork/common/channel/ExceptionHandler.java b/urban-spork-common/src/com/urbanspork/common/channel/ExceptionHandler.java index edcf7c42..8507fea3 100644 --- a/urban-spork-common/src/com/urbanspork/common/channel/ExceptionHandler.java +++ b/urban-spork-common/src/com/urbanspork/common/channel/ExceptionHandler.java @@ -30,8 +30,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { if (cause.getCause() instanceof InvalidCipherTextException) { logger.error("[{}][{}][{}] Invalid cipher text", transport, protocol, transLog); } else { - String msg = String.format("[%s][%s][%s] Caught exception", transport, protocol, transLog); - logger.error(msg, cause); + logger.error("[{}][{}][{}] Caught exception", transport, protocol, transLog, cause); } ctx.close(); } diff --git a/urban-spork-common/src/com/urbanspork/common/config/SslSetting.java b/urban-spork-common/src/com/urbanspork/common/config/SslSetting.java index 087a11f3..6bc64baf 100644 --- a/urban-spork-common/src/com/urbanspork/common/config/SslSetting.java +++ b/urban-spork-common/src/com/urbanspork/common/config/SslSetting.java @@ -6,6 +6,7 @@ public class SslSetting { private String keyFile; private String keyPassword; private String serverName; + private boolean verifyHostname = true; public String getCertificateFile() { return certificateFile; @@ -38,4 +39,12 @@ public String getServerName() { public void setServerName(String serverName) { this.serverName = serverName; } + + public boolean isVerifyHostname() { + return verifyHostname; + } + + public void setVerifyHostname(boolean verifyHostname) { + this.verifyHostname = verifyHostname; + } } diff --git a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/AEAD2022.java b/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/AEAD2022.java index defb7bf0..747d0297 100644 --- a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/AEAD2022.java +++ b/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/AEAD2022.java @@ -232,7 +232,7 @@ static int getNonceLength(CipherKind kind) { } static UdpCipher getCipher(CipherKind kind, CipherMethod method, byte[] key, long sessionId) { - return UdpCipherCaches.INSTANCE.get(kind, method, key, sessionId); + return UdpCipherCache.INSTANCE.get(kind, method, key, sessionId); } static void encodePacket(UdpCipher cipher, byte[] iPSK, int eihLength, ByteBuf in, ByteBuf out) throws InvalidCipherTextException { diff --git a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCache.java b/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCache.java index b1aa98c1..719bdc16 100644 --- a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCache.java +++ b/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCache.java @@ -2,41 +2,25 @@ import com.urbanspork.common.codec.CipherKind; import com.urbanspork.common.codec.aead.CipherMethod; -import io.netty.util.HashedWheelTimer; +import com.urbanspork.common.util.LruCache; -import java.time.Duration; import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; -public class UdpCipherCache { - private final HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS); - private final LinkedHashMap map = new LinkedHashMap<>() { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return map.size() > limit; - } - }; - private final Duration duration; - private final int limit; +public enum UdpCipherCache { + INSTANCE(new LruCache<>(AEAD2022.UDP.CIPHER_CACHE_LIMIT, AEAD2022.UDP.CIPHER_CACHE_DURATION, (k, v) -> {})); - public UdpCipherCache(Duration duration, int limit) { - this.duration = duration; - this.limit = limit; - } + private final LruCache cache; - public UdpCipher computeIfAbsent(CipherKind kind, CipherMethod method, byte[] key, long sessionId) { - Key cacheKey = new Key(kind, key, sessionId); - return map.computeIfAbsent(cacheKey, k -> { - timer.newTimeout(timeout -> map.remove(cacheKey), duration.toSeconds(), TimeUnit.SECONDS); - return new UdpCipher(method, AEAD2022.UDP.sessionSubkey(key, sessionId)); - }); + UdpCipherCache(LruCache cache) { + this.cache = cache; } - public boolean contains(CipherKind kind, byte[] key, long sessionId) { - return map.containsKey(new Key(kind, key, sessionId)); + public UdpCipher get(CipherKind kind, CipherMethod method, byte[] key, long sessionId) { + return cache.computeIfAbsent( + new Key(kind, key, sessionId), + k -> new UdpCipher(method, AEAD2022.UDP.sessionSubkey(key, sessionId)) + ); } record Key(CipherKind kind, byte[] key, long sessionId) { diff --git a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCaches.java b/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCaches.java deleted file mode 100644 index 30bc8d23..00000000 --- a/urban-spork-common/src/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCaches.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.urbanspork.common.protocol.shadowsocks.aead2022; - -import com.urbanspork.common.codec.CipherKind; -import com.urbanspork.common.codec.aead.CipherMethod; - -public enum UdpCipherCaches { - INSTANCE(new UdpCipherCache(AEAD2022.UDP.CIPHER_CACHE_DURATION, AEAD2022.UDP.CIPHER_CACHE_LIMIT)); - - private final UdpCipherCache cache; - - UdpCipherCaches(UdpCipherCache cache) { - this.cache = cache; - } - - public UdpCipher get(CipherKind kind, CipherMethod method, byte[] key, long sessionId) { - return cache.computeIfAbsent(kind, method, key, sessionId); - } -} diff --git a/urban-spork-server/pom.xml b/urban-spork-server/pom.xml index 4e8df4ab..c0ce9c58 100644 --- a/urban-spork-server/pom.xml +++ b/urban-spork-server/pom.xml @@ -3,7 +3,7 @@ urban-spork urban-spork - 2.4.4 + 2.4.5 urban-spork-server diff --git a/urban-spork-server/src/com/urbanspork/server/Server.java b/urban-spork-server/src/com/urbanspork/server/Server.java index 283d2f1a..8513a5cc 100644 --- a/urban-spork-server/src/com/urbanspork/server/Server.java +++ b/urban-spork-server/src/com/urbanspork/server/Server.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -127,7 +128,8 @@ protected void initChannel(Channel ch) { } } - public record Instance(ServerSocketChannel tcp, Optional udp) { + public record Instance(ServerSocketChannel tcp, Optional udp) implements Closeable { + @Override public void close() { tcp.close().awaitUninterruptibly(); udp.ifPresent(c -> c.close().awaitUninterruptibly()); diff --git a/urban-spork-test/pom.xml b/urban-spork-test/pom.xml index 0302f395..8c55203e 100644 --- a/urban-spork-test/pom.xml +++ b/urban-spork-test/pom.xml @@ -6,7 +6,7 @@ urban-spork urban-spork - 2.4.4 + 2.4.5 urban-spork-test diff --git a/urban-spork-test/test/com/urbanspork/client/ClientSocksInitializerTest.java b/urban-spork-test/test/com/urbanspork/client/ClientSocksInitializerTest.java index 83a33c9d..07de877a 100644 --- a/urban-spork-test/test/com/urbanspork/client/ClientSocksInitializerTest.java +++ b/urban-spork-test/test/com/urbanspork/client/ClientSocksInitializerTest.java @@ -13,7 +13,9 @@ void testBuildSslHandler() { EmbeddedChannel channel = new EmbeddedChannel(); ServerConfig config = ServerConfigTest.testConfig(0); Assertions.assertDoesNotThrow(() -> ClientSocksInitializer.buildSslHandler(channel, config)); - config.setSsl(new SslSetting()); + SslSetting ssl = new SslSetting(); + ssl.setVerifyHostname(false); + config.setSsl(ssl); Assertions.assertDoesNotThrow(() -> ClientSocksInitializer.buildSslHandler(channel, config)); } } diff --git a/urban-spork-test/test/com/urbanspork/common/config/SslSettingTest.java b/urban-spork-test/test/com/urbanspork/common/config/SslSettingTest.java index 8bd173f6..0816d7fc 100644 --- a/urban-spork-test/test/com/urbanspork/common/config/SslSettingTest.java +++ b/urban-spork-test/test/com/urbanspork/common/config/SslSettingTest.java @@ -11,5 +11,6 @@ void testGetterAndSetter() { TestUtil.testGetterAndSetter("B", setting, SslSetting::getKeyFile, SslSetting::setKeyFile); TestUtil.testGetterAndSetter("C", setting, SslSetting::getKeyPassword, SslSetting::setKeyPassword); TestUtil.testGetterAndSetter("D", setting, SslSetting::getServerName, SslSetting::setServerName); + TestUtil.testGetterAndSetter(false, setting, SslSetting::isVerifyHostname, SslSetting::setVerifyHostname); } } diff --git a/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPAuthCacheTest.java b/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPAuthCacheTest.java deleted file mode 100644 index e3d0e944..00000000 --- a/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPAuthCacheTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.urbanspork.common.protocol.shadowsocks.aead2022; - -import com.urbanspork.common.codec.CipherKind; -import com.urbanspork.common.codec.aead.CipherMethod; -import com.urbanspork.common.codec.aead.CipherMethods; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -class UDPAuthCacheTest { - @Test - void testDuration() { - CipherKind kind = CipherKind.aead2022_blake3_aes_128_gcm; - CipherMethod method = CipherMethods.AES_GCM.get(); - byte[] key = new byte[]{1}; - long sessionId = 1; - UdpCipherCache cache = new UdpCipherCache(Duration.ofSeconds(1), 2); - UdpCipher auth1 = cache.computeIfAbsent(kind, method, key, sessionId); - UdpCipher auth2 = cache.computeIfAbsent(kind, method, key, sessionId); - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3)); - UdpCipher auth3 = cache.computeIfAbsent(kind, method, key, sessionId); - Assertions.assertEquals(auth1, auth2); - Assertions.assertNotEquals(auth2, auth3); - } - - @Test - void testLimit() { - CipherKind kind = CipherKind.aead2022_blake3_aes_128_gcm; - CipherMethod method = CipherMethods.AES_GCM.get(); - byte[] key = new byte[]{1}; - UdpCipherCache cache = new UdpCipherCache(Duration.ofHours(1), 2); - cache.computeIfAbsent(kind, method, key, 1); - cache.computeIfAbsent(kind, method, key, 2); - cache.computeIfAbsent(kind, method, key, 3); - cache.computeIfAbsent(kind, method, key, 4); - Assertions.assertFalse(cache.contains(kind, key, 1)); - Assertions.assertFalse(cache.contains(kind, key, 2)); - Assertions.assertTrue(cache.contains(kind, key, 3)); - Assertions.assertTrue(cache.contains(kind, key, 4)); - } -} diff --git a/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPCipherCacheTest.java b/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCacheTest.java similarity index 97% rename from urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPCipherCacheTest.java rename to urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCacheTest.java index 7308983f..4ce3e23c 100644 --- a/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UDPCipherCacheTest.java +++ b/urban-spork-test/test/com/urbanspork/common/protocol/shadowsocks/aead2022/UdpCipherCacheTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -class UDPCipherCacheTest { +class UdpCipherCacheTest { @Test void testKey() { CipherKind kind = CipherKind.aead2022_blake3_aes_128_gcm;