Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.4.2 #18

Merged
merged 10 commits into from
Feb 1, 2024
Merged
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>urban-spork</groupId>
<artifactId>urban-spork</artifactId>
<version>2.4.1</version>
<version>2.4.2</version>
<packaging>pom</packaging>
<modules>
<module>urban-spork-common</module>
Expand All @@ -14,7 +14,7 @@
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<urban-spork.version>2.4.1</urban-spork.version>
<urban-spork.version>2.4.2</urban-spork.version>
<maven-surefire-plugin.versioin>3.2.3</maven-surefire-plugin.versioin>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
Expand Down
2 changes: 1 addition & 1 deletion urban-spork-client-gui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>urban-spork</groupId>
<artifactId>urban-spork</artifactId>
<version>2.4.1</version>
<version>2.4.2</version>
</parent>
<groupId>urban-spork-client-gui</groupId>
<artifactId>urban-spork-client-gui</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import com.urbanspork.common.config.ConfigHandler;

import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;

public class Resource {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@
import com.urbanspork.client.gui.Resource;
import com.urbanspork.common.config.ClientConfig;
import com.urbanspork.common.config.ServerConfig;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.util.concurrent.DefaultPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.TrayIcon.MessageType;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Proxy {

private static final Logger logger = LoggerFactory.getLogger(Proxy.class);

private static final ClientConfig config = Resource.config();

private static final ExecutorService executor = Executors.newSingleThreadExecutor();

private static Future<?> proxyingTask;
private static Map.Entry<ServerSocketChannel, DatagramChannel> client;

private Proxy() {}

Expand All @@ -32,29 +30,27 @@ public static void launch() {
Tray.displayMessage("Proxy is not running", "Please set up a proxy server first", MessageType.INFO);
return;
}
if (proxyingTask != null && !proxyingTask.isCancelled()) {
proxyingTask.cancel(true);
if (client != null) {
Client.close(client);
}
CompletableFuture<Map.Entry<ServerSocketChannel, DatagramChannel>> promise = new CompletableFuture<>();
executor.submit(() -> Client.launch(config, promise));
try {
client = promise.get();
String message = current.toString();
Tray.displayMessage("Proxy is running", message, MessageType.INFO);
Tray.setToolTip(message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
String message = e.getMessage();
Tray.displayMessage("Error", message, MessageType.ERROR);
Tray.setToolTip(message);
}
proxyingTask = executor.submit(() -> {
try {
DefaultPromise<ServerSocketChannel> promise = new DefaultPromise<>() {};
Client.launch(config, promise);
promise.await();
} catch (InterruptedException e) {
logger.error("Launching proxy client launching error", e);
String message = e.getMessage();
Tray.displayMessage("Error", message, MessageType.ERROR);
Tray.setToolTip(message);
Thread.currentThread().interrupt();
}
});
String message = current.toString();
Tray.displayMessage("Proxy is running", message, MessageType.INFO);
Tray.setToolTip(message);
}

public static void exit() {
proxyingTask.cancel(true);
Client.close(client);
executor.shutdown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ private void itemStateChanged(ClientConfig config, List<CheckboxMenuItem> items,
return;
}
Proxy.launch();
String message = config.getCurrent().toString();
Tray.displayMessage("Proxy is running", message, MessageType.INFO);
Tray.setToolTip(message);
} else {
item.setState(true);
}
Expand Down
2 changes: 1 addition & 1 deletion urban-spork-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>urban-spork</groupId>
<artifactId>urban-spork</artifactId>
<version>2.4.1</version>
<version>2.4.2</version>
</parent>
<artifactId>urban-spork-client</artifactId>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
package com.urbanspork.client;

import com.urbanspork.common.channel.ChannelCloseUtils;
import com.urbanspork.common.config.ServerConfig;
import com.urbanspork.common.protocol.network.TernaryDatagramPacket;
import com.urbanspork.common.util.LruCache;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.HashedWheelTimer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.time.Duration;

public abstract class AbstractClientUDPReplayHandler<K> extends SimpleChannelInboundHandler<TernaryDatagramPacket> {
public abstract class AbstractClientUdpRelayHandler<K> extends SimpleChannelInboundHandler<TernaryDatagramPacket> {

private static final Logger logger = LoggerFactory.getLogger(AbstractClientUDPReplayHandler.class);
private final HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS);
private final Map<K, Channel> binding = new ConcurrentHashMap<>();
private static final Logger logger = LoggerFactory.getLogger(AbstractClientUdpRelayHandler.class);
protected final ServerConfig config;
private final Duration keepAlive = Duration.ofMinutes(10);
private final LruCache<K, Channel> binding = new LruCache<>(1024, keepAlive, (k, channel) -> {
logger.info("[udp][binding][expire]{} != {}", k, channel.localAddress());
channel.close();
});

Check warning on line 23 in urban-spork-client/src/com/urbanspork/client/AbstractClientUdpRelayHandler.java

View check run for this annotation

Codecov / codecov/patch

urban-spork-client/src/com/urbanspork/client/AbstractClientUdpRelayHandler.java#L21-L23

Added lines #L21 - L23 were not covered by tests

protected AbstractClientUDPReplayHandler(ServerConfig config) {
super(false);
protected AbstractClientUdpRelayHandler(ServerConfig config) {
this.config = config;
}

Expand All @@ -43,27 +42,22 @@
}

@Override
public void channelUnregistered(ChannelHandlerContext ctx) {
ctx.fireChannelUnregistered();
logger.info("Stop timer and clean binding");
timer.stop();
ChannelCloseUtils.clearMap(binding);
public void handlerRemoved(ChannelHandlerContext ctx) {
logger.info("Stop timer and clear binding");
binding.clear();
}

private Channel getBindingChannel(Channel inboundChannel, K key) {
return binding.computeIfAbsent(key, k -> {
Channel channel = newBindingChannel(inboundChannel, key);
logger.info("[udp][binding]{} = {}", key, channel.localAddress());
timer.newTimeout(timeout -> channel.close(), 10, TimeUnit.MINUTES);
timer.newTimeout(timeout -> binding.remove(key), 1, TimeUnit.MINUTES);
Channel channel = binding.get(key);
if (channel == null) {
channel = newBindingChannel(inboundChannel, key);
channel.closeFuture().addListener(future -> {
Channel removed = binding.remove(key);
if (removed != null) {
logger.info("[udp][binding]{} != {}", key, removed.localAddress());
logger.info("[udp][binding][close]{} != {}", key, removed.localAddress());

Check warning on line 57 in urban-spork-client/src/com/urbanspork/client/AbstractClientUdpRelayHandler.java

View check run for this annotation

Codecov / codecov/patch

urban-spork-client/src/com/urbanspork/client/AbstractClientUdpRelayHandler.java#L57

Added line #L57 was not covered by tests
}
});
return channel;
});
}
return channel;
}

}
96 changes: 60 additions & 36 deletions urban-spork-client/src/com/urbanspork/client/Client.java
Original file line number Diff line number Diff line change
@@ -1,78 +1,102 @@
package com.urbanspork.client;

import com.urbanspork.client.shadowsocks.ClientUDPReplayHandler;
import com.urbanspork.client.vmess.ClientUDPOverTCPHandler;
import com.urbanspork.common.channel.AttributeKeys;
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;
import com.urbanspork.common.config.ConfigHandler;
import com.urbanspork.common.config.ServerConfig;
import com.urbanspork.common.protocol.Protocols;
import com.urbanspork.common.protocol.network.Direction;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class Client {

private static final Logger logger = LoggerFactory.getLogger(Client.class);

public static void main(String[] args) {
launch(ConfigHandler.DEFAULT.read(), new DefaultPromise<>() {});
launch(ConfigHandler.DEFAULT.read(), new CompletableFuture<>());
}

public static void launch(ClientConfig config, Promise<ServerSocketChannel> promise) {
int port = config.getPort();
ServerConfig current = config.getCurrent();
public static void launch(ClientConfig config, CompletableFuture<Map.Entry<ServerSocketChannel, DatagramChannel>> promise) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ChannelHandler udpTransportHandler;
if (Protocols.vmess == current.getProtocol()) {
udpTransportHandler = new ClientUDPOverTCPHandler(current, workerGroup);
} else {
udpTransportHandler = new ClientUDPReplayHandler(current, workerGroup);
}
new Bootstrap().group(bossGroup).channel(NioDatagramChannel.class)
.attr(AttributeKeys.DIRECTION, Direction.Inbound)
.handler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(
new DatagramPacketEncoder(),
new DatagramPacketDecoder(),
udpTransportHandler
);
}
})
.bind(port).sync();
new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true) // socks5 require
.childOption(ChannelOption.TCP_NODELAY, false)
.childOption(ChannelOption.SO_LINGER, 1)
.childHandler(new ClientSocksInitializer(current, port))
.bind(port).sync().addListener((ChannelFutureListener) future -> {
logger.info("Launch client => {} ", config);
promise.setSuccess((ServerSocketChannel) future.channel());
}).channel().closeFuture().sync();
.childHandler(new ClientSocksInitializer(config.getCurrent()))
.bind(InetAddress.getLoopbackAddress(), config.getPort()).sync().addListener((ChannelFutureListener) future -> {
ServerSocketChannel tcp = (ServerSocketChannel) future.channel();
InetSocketAddress tcpLocalAddress = tcp.localAddress();
int localPort = tcpLocalAddress.getPort();
config.setPort(localPort);
DatagramChannel udp = launchUdp(bossGroup, workerGroup, config);
logger.info("Launch client => tcp{} udp{} ", tcpLocalAddress, udp.localAddress());
Map.Entry<ServerSocketChannel, DatagramChannel> client = Map.entry(tcp, udp);
promise.complete(client);
});
Map.Entry<ServerSocketChannel, DatagramChannel> client = promise.get();
CompletableFuture.allOf(
CompletableFuture.supplyAsync(() -> client.getKey().closeFuture().syncUninterruptibly()),
CompletableFuture.supplyAsync(() -> client.getValue().closeFuture().syncUninterruptibly())
).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.error("Launch client failed", e);
promise.setFailure(e);
promise.completeExceptionally(e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}

public static void close(Map.Entry<ServerSocketChannel, DatagramChannel> client) {
client.getKey().close().awaitUninterruptibly();
client.getValue().close().awaitUninterruptibly();
}

private static DatagramChannel launchUdp(EventLoopGroup bossGroup, EventLoopGroup workerGroup, ClientConfig config) throws InterruptedException {
ServerConfig current = config.getCurrent();
ChannelHandler udpTransportHandler;
if (Protocols.vmess == current.getProtocol()) {
udpTransportHandler = new ClientUdpOverTCPHandler(current, workerGroup);
} else {
udpTransportHandler = new ClientUdpRelayHandler(current, workerGroup);
}
return (DatagramChannel) new Bootstrap().group(bossGroup).channel(NioDatagramChannel.class)
.handler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(
new DatagramPacketEncoder(),
new DatagramPacketDecoder(),
udpTransportHandler
);
}
})
.bind(InetAddress.getLoopbackAddress(), config.getPort()).sync().channel();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
import com.urbanspork.common.channel.DefaultChannelInboundHandler;
import com.urbanspork.common.config.ServerConfig;
import com.urbanspork.common.protocol.Protocols;
import com.urbanspork.common.protocol.socks.Socks5;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
import io.netty.handler.codec.socksx.v5.Socks5CommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -42,8 +49,8 @@ protected void channelRead0(ChannelHandlerContext ctx, Socks5CommandRequest requ
Channel outbound = future.channel();
outbound.pipeline().addLast(new DefaultChannelInboundHandler(ctx.channel())); // R → L
ctx.pipeline().remove(ClientSocksConnectHandler.this);
InetSocketAddress localAddress = (InetSocketAddress) inboundChannel.localAddress();
ctx.writeAndFlush(new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, request.dstAddrType(), localAddress.getHostString(), localAddress.getPort()))
Socks5CommandRequest bndRequest = Socks5.toCommandRequest(Socks5CommandType.CONNECT, (InetSocketAddress) inboundChannel.localAddress());
ctx.writeAndFlush(new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, bndRequest.dstAddrType(), bndRequest.dstAddr(), bndRequest.dstPort()))
.addListener((ChannelFutureListener) channelFuture -> ctx.pipeline().addLast(new DefaultChannelInboundHandler(outbound))); // L → R
} else {
logger.error("Connect proxy server {} failed", serverAddress);
Expand Down
Loading
Loading