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.4.2 #26

Merged
merged 1 commit into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,68 @@

A sock5 proxy

## Quick start

put *config.json* file into the unpacked folder before running
## Config
put *config.json* file into the unpacked folder before running server

```json5
{
"servers": [
{
"cipher": "{cipher}",
"password": "{password}",
"port": "{port}",
"protocol": "{protocol}",
"packetEncoding": "{packetEncoding}",
"cipher": "aes-128-gcm",
"password": "foobar",
"host": "example.com",
"port": "443",
"protocol": "shadowsocks",
"packetEncoding": "None",
"transport": [
"{transport}"
"TCP",
"UDP"
],
"user": [
{
"name": "username",
"password": "{user password}"
"name": "John Doe",
"password": "foobar"
}
]
],
"ssl": {
"certificateFile": "/path/to/certificate.crt",
"keyFile": "/path/to/private.key",
"keyPassword": "",
"serverName": ""
}
}
]
}
```

> `protocol`: "shadowsocks" | "vmess"
> `protocol`: "shadowsocks" | "vmess" | "trojan"

> `cipher`: see *Ciphers*

> `transport`: see *Transport*

> `packetEncoding`: "None" | "Packet"

> `user`: (OPTIONAL) support multiple users with [*Shadowsocks 2022 Extensible Identity Headers*](https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md)
> `user`: (OPTIONAL for shadowsocks) support multiple users with [*Shadowsocks 2022 Extensible Identity Headers*](https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md)

> `sslSetting`: (REQUIRED for trojan) SSL specific configurations

>> `certificateFile`: certificate file

>> `keyFile`: private key file for encryption

>> `keyPassword`: password of the private key file

>> `serverName`: the Server Name Indication field in the SSL handshake. If left blank, it will be set to `server.host`

## Features

### Transport

| | Shadowsocks | VMess |
|:----|:-----------:|:-----:|
| TCP | ✔ | ✔ |
| UDP | ✔ | ✔ |
| | Shadowsocks | VMess | Trojan |
|:----|:-----------:|:-----:|:------:|
| TCP | ✔ | ✔ | ✔ |
| UDP | ✔ | ✔ | ✔ |

### Ciphers

Expand Down
5 changes: 1 addition & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-lts8on</artifactId>
<artifactId>bcpkix-lts8on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
Expand Down Expand Up @@ -123,9 +123,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.versioin}</version>
<configuration>
<forkCount>1</forkCount>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.urbanspork.common.protocol.Protocol;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
Expand Down Expand Up @@ -73,7 +74,7 @@ public class Console extends Application {

Tray tray;
Proxy proxy;
ObjectProperty<Client.Instance> instance = new SimpleObjectProperty<>();
final ObjectProperty<Client.Instance> instance = new SimpleObjectProperty<>();

private Stage primaryStage;
private JFXTabPane root;
Expand Down Expand Up @@ -236,18 +237,6 @@ public void confirmServerConfig() {
}
}

public void cancelServerConfig() {
hide();
int lastIndex = serverConfigObservableList.size() - 1;
if (lastIndex > -1) {
ServerConfig lastConfig = serverConfigObservableList.get(lastIndex);
if (!lastConfig.check()) {
serverConfigObservableList.remove(lastIndex);
}
serverConfigJFXListView.getSelectionModel().select(CLIENT_CONFIG.getCurrent());
}
}

private void initWidget() {
serverConfigJFXListView = new ServerConfigListView();
logTextArea = initLogTextArea();
Expand All @@ -259,7 +248,7 @@ private void initWidget() {
moveUpServerConfigButton = new ConsoleLiteButton(I18N.getString(I18N.CONSOLE_BUTTON_UP), event -> moveUpServerConfig());
moveDownServerConfigButton = new ConsoleLiteButton(I18N.getString(I18N.CONSOLE_BUTTON_DOWN), event -> moveDownServerConfig());
confirmServerConfigButton = new ConsoleButton(I18N.getString(I18N.CONSOLE_BUTTON_CONFIRM), event -> confirmServerConfig());
cancelServerConfigButton = new ConsoleButton(I18N.getString(I18N.CONSOLE_BUTTON_CANCEL), event -> cancelServerConfig());
cancelServerConfigButton = new ConsoleButton(I18N.getString(I18N.CONSOLE_BUTTON_CANCEL), event -> hide());
currentConfigHostTextField = new ConsoleTextField();
currentConfigPortTextField = new NumericTextField();
currentConfigPasswordPasswordField = new JFXPasswordField();
Expand Down Expand Up @@ -436,6 +425,8 @@ private void initController() {
initCurrentConfigPasswordTextField();
initCurrentConfigPasswordPasswordField();
initClientConfigPortTextField();
initShareServerConfigButton();
initImportServerConfigButton();
display();
}

Expand Down Expand Up @@ -495,6 +486,7 @@ private void initCurrentConfigCipherChoiceBox() {
List<CipherKind> ciphers = Arrays.asList(CipherKind.values());
currentConfigCipherChoiceBox.setItems(FXCollections.observableArrayList(ciphers));
currentConfigCipherChoiceBox.setValue(CipherKind.aes_128_gcm);
currentConfigCipherChoiceBox.disableProperty().bind(Bindings.equal(Protocol.trojan, currentConfigProtocolChoiceBox.valueProperty()));
// currentConfigHostTextField
currentConfigHostTextField.getValidators().add(REQUIRED_FIELD_VALIDATOR);
currentConfigHostTextField.textProperty().addListener((o, oldValue, newValue) -> currentConfigHostTextField.validate());
Expand All @@ -506,6 +498,14 @@ private void initCurrentConfigProtocolChoiceBox() {
currentConfigProtocolChoiceBox.setValue(Protocol.shadowsocks);
}

private void initShareServerConfigButton() {
shareServerConfigButton.visibleProperty().bind(Bindings.equal(Protocol.shadowsocks, currentConfigProtocolChoiceBox.valueProperty()));
}

private void initImportServerConfigButton() {
importServerConfigButton.visibleProperty().bind(Bindings.equal(Protocol.shadowsocks, currentConfigProtocolChoiceBox.valueProperty()));
}

private void initServerConfigListView() {
serverConfigObservableList = FXCollections.observableArrayList(CLIENT_CONFIG.getServers());
CLIENT_CONFIG.setServers(serverConfigObservableList);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.urbanspork.client;

import com.urbanspork.common.channel.DefaultChannelInboundHandler;
import com.urbanspork.common.config.ServerConfig;
import com.urbanspork.common.transport.udp.DatagramPacketWrapper;
import io.netty.bootstrap.Bootstrap;
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.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.time.Duration;

public abstract class AbstractClientUdpOverTcpHandler<K> extends AbstractClientUdpRelayHandler<K> {

private static final Logger logger = LoggerFactory.getLogger(AbstractClientUdpOverTcpHandler.class);
private final EventLoopGroup workerGroup;

protected AbstractClientUdpOverTcpHandler(ServerConfig config, Duration keepAlive, EventLoopGroup workerGroup) {
super(config, keepAlive);
this.workerGroup = workerGroup;
}

protected abstract Object convertToWrite(DatagramPacketWrapper msg);

protected abstract K getKey(DatagramPacketWrapper msg);

protected abstract ChannelInitializer<Channel> newOutboundInitializer(K key);

protected abstract ChannelHandler newInboundHandler(Channel inboundChannel, K key);

@Override
protected Channel newBindingChannel(Channel inboundChannel, K key) {
InetSocketAddress serverAddress = new InetSocketAddress(config.getHost(), config.getPort());
return new Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.handler(newOutboundInitializer(key))
.connect(serverAddress).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
Channel outbound = future.channel();
outbound.pipeline().addLast(newInboundHandler(inboundChannel, key)); // R → L
inboundChannel.pipeline().addLast(new DefaultChannelInboundHandler(outbound)); // L → R
} else {
logger.error("Connect relay server {} failed", serverAddress);
}
}).syncUninterruptibly().channel();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class AbstractClientUdpRelayHandler<K> extends SimpleChannelInbo

private static final Logger logger = LoggerFactory.getLogger(AbstractClientUdpRelayHandler.class);
protected final ServerConfig config;
private final LruCache<K, Channel> binding;
protected final LruCache<K, Channel> binding;

protected AbstractClientUdpRelayHandler(ServerConfig config, Duration keepAlive) {
super(false);
Expand All @@ -38,7 +38,7 @@ public void channelRead0(ChannelHandlerContext ctx, DatagramPacketWrapper msg) {
DatagramPacket packet = msg.packet();
Channel inbound = ctx.channel();
Channel outbound = getBindingChannel(inbound, getKey(msg));
logger.info("[udp][{}]{}→{}~{}→{}", config.getProtocol(), packet.sender(), inbound.localAddress(), outbound.localAddress(), msg.proxy());
logger.info("[udp][{}]{}→{}~{}→{}", config.getProtocol(), packet.sender(), msg.proxy(), inbound.localAddress(), outbound.localAddress());
outbound.writeAndFlush(convertToWrite(msg));
}

Expand Down
10 changes: 6 additions & 4 deletions urban-spork-client/src/com/urbanspork/client/Client.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.urbanspork.client;

import com.urbanspork.client.shadowsocks.ClientUdpRelayHandler;
import com.urbanspork.client.vmess.ClientUdpOverTCPHandler;
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;
Expand Down Expand Up @@ -81,7 +81,9 @@ 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 ClientUdpOverTcpHandler(current, workerGroup);
} else if (Protocol.trojan == current.getProtocol()) {
udpTransportHandler = new com.urbanspork.client.trojan.ClientUdpOverTcpHandler(current, workerGroup);
} else {
udpTransportHandler = new ClientUdpRelayHandler(current, workerGroup);
}
Expand All @@ -90,10 +92,10 @@ private static DatagramChannel launchUdp(EventLoopGroup bossGroup, EventLoopGrou
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(
current.getTrafficShapingHandler(),
new DatagramPacketEncoder(),
new DatagramPacketDecoder(),
udpTransportHandler,
current.getTrafficShapingHandler()
udpTransportHandler
);
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.urbanspork.client;

import com.urbanspork.client.vmess.ClientAEADCodec;
import com.urbanspork.client.trojan.ClientHeaderEncoder;
import com.urbanspork.client.vmess.ClientAeadCodec;
import com.urbanspork.common.channel.AttributeKeys;
import com.urbanspork.common.channel.ChannelCloseUtils;
import com.urbanspork.common.channel.DefaultChannelInboundHandler;
import com.urbanspork.common.codec.shadowsocks.Mode;
import com.urbanspork.common.codec.shadowsocks.tcp.Context;
import com.urbanspork.common.codec.shadowsocks.tcp.TcpRelayCodec;
import com.urbanspork.common.config.ServerConfig;
import com.urbanspork.common.protocol.Protocol;
import com.urbanspork.common.protocol.socks.Socks5;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
Expand All @@ -18,13 +18,15 @@
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.socks.SocksCmdType;
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;

import javax.net.ssl.SSLException;
import java.net.InetSocketAddress;

public class ClientSocksConnectHandler extends SimpleChannelInboundHandler<Socks5CommandRequest> {
Expand Down Expand Up @@ -58,20 +60,30 @@ protected void channelRead0(ChannelHandlerContext ctx, Socks5CommandRequest requ
}

private static ChannelHandler getOutboundChannelHandler(Socks5CommandRequest request, ServerConfig config) {
if (Protocol.vmess == config.getProtocol()) {
return new ChannelInitializer<>() {
return switch (config.getProtocol()) {
case vmess -> new ChannelInitializer<>() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(new ClientAEADCodec(config.getCipher(), InetSocketAddress.createUnresolved(request.dstAddr(), request.dstPort()), config.getPassword()));
InetSocketAddress address = InetSocketAddress.createUnresolved(request.dstAddr(), request.dstPort());
channel.pipeline().addLast(new ClientAeadCodec(config.getCipher(), address, config.getPassword()));
}
};
} else {
return new ChannelInitializer<>() {
case trojan -> new ChannelInitializer<>() {
@Override
protected void initChannel(Channel channel) throws SSLException {
InetSocketAddress address = InetSocketAddress.createUnresolved(request.dstAddr(), request.dstPort());
channel.pipeline().addLast(
ClientSocksInitializer.buildSslHandler(channel, config),
new ClientHeaderEncoder(config.getPassword(), address, SocksCmdType.CONNECT.byteValue())
);
}
};
default -> new ChannelInitializer<>() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(new TcpRelayCodec(new Context(), config, request, Mode.Client));
}
};
}
};
}
}
Loading
Loading