├── .classpath
├── .gitignore
├── .project
├── Readme.md
├── config.xml
├── openvpn.eddx
├── openvpn.png
├── pom.xml
└── src
└── main
├── java
├── com
│ └── chinaums
│ │ └── xtunnel
│ │ ├── ConfigXMLParser.java
│ │ ├── EndpointConfig.java
│ │ ├── MasterChannelInitializer.java
│ │ ├── MasterTransferHandler.java
│ │ ├── SSLUtil.java
│ │ ├── SlaveChannelInitializer.java
│ │ ├── SlaveTransferHandler.java
│ │ ├── SystrayUtil.java
│ │ ├── TunnelConfig.java
│ │ └── XTunnel.java
├── icon.png
├── logback.xml
└── xtunnel.jks
└── resources
├── icon.png
├── logback.xml
└── xtunnel.jks
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | /.settings/
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | xtunnel
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.m2e.core.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # XTunnel
2 |
3 | XTunnel是一个TCP隧道工具,通过部署中间节点使两个无法直接联通的节点可以正常通讯,类似SSH的TUNNEL功能。
4 |
5 | ## 特性
6 | * 支持SSL加密功能,可以在两个XTunnel之间采用SSL连接,突破防火墙的报文内容侦测。
7 | * 同时支持多组转发配置,采用Netty异步框架,有很高的性能。
8 |
9 | ## XTunnel + OpenVPN
10 | 
11 |
12 | ## 配置文件
13 |
14 | ```xml
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ```
27 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/openvpn.eddx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/openvpn.eddx
--------------------------------------------------------------------------------
/openvpn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/openvpn.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.chinaums
5 | xtunnel
6 | 1.0.0-SNAPSHOT
7 |
8 |
9 | UTF-8
10 |
11 | 4.1.6.RELEASE
12 | 1.8.0.RELEASE
13 | 4.2.3.Final
14 | 5.1.3.Final
15 | 2.4.3
16 | 3.6.2
17 | 3.4.2
18 | 3.3.0
19 | 1.2.2
20 | 1.16.4
21 | 2.5.3
22 | 1.1.10
23 | 0.1
24 | 3.4.6
25 | 1.1.3
26 | 1.7.12
27 | 2.7.8
28 | 0.12
29 | 1.2.5
30 | 1.0.14
31 | 3.1
32 | 2.4
33 | 18.0
34 | 4.0.28.Final
35 | 4.1
36 |
37 |
38 |
39 | xtunnel
40 | src/main/java
41 |
42 |
43 |
44 | src/main/resources
45 | ${project.build.directory}
46 |
47 |
48 |
49 |
50 |
51 | org.apache.maven.plugins
52 | maven-compiler-plugin
53 | 3.1
54 |
55 | 1.6
56 | 1.6
57 | ${project.build.sourceEncoding}
58 |
59 |
60 |
61 |
62 | org.apache.maven.plugins
63 | maven-resources-plugin
64 | 2.6
65 |
66 | ${project.build.sourceEncoding}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | org.slf4j
75 | slf4j-api
76 | ${slf4j.version}
77 |
78 |
79 | ch.qos.logback
80 | logback-classic
81 | ${logback.version}
82 |
83 |
84 | commons-io
85 | commons-io
86 | 2.4
87 |
88 |
89 | commons-lang
90 | commons-lang
91 | 2.6
92 |
93 |
94 | commons-logging
95 | commons-logging
96 | 1.1.1
97 |
98 |
99 | jaxen
100 | jaxen
101 | 1.1.6
102 |
103 |
104 | dom4j
105 | dom4j
106 | 1.6.1
107 |
108 |
109 | io.netty
110 | netty-all
111 | ${netty.version}
112 |
113 |
114 | org.projectlombok
115 | lombok
116 | ${lombok.version}
117 | provided
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/ConfigXMLParser.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import java.io.InputStream;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import org.dom4j.Document;
8 | import org.dom4j.Node;
9 | import org.dom4j.io.SAXReader;
10 |
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | @Slf4j
14 | public class ConfigXMLParser {
15 | public List parse(InputStream is) throws Exception {
16 | log.info("Parse config file.");
17 | List list = new ArrayList();
18 |
19 | SAXReader reader = new SAXReader();
20 | Document doc = reader.read(is);
21 |
22 | List tunnelNodes = doc.selectNodes("/config/tunnel");
23 |
24 | for (Node tunnel : tunnelNodes) {
25 | TunnelConfig tunnelConfig = new TunnelConfig();
26 | tunnelConfig.setName(tunnel.selectSingleNode("@name")
27 | .getStringValue());
28 | tunnelConfig.setLocal(parseEndpoint(tunnel
29 | .selectSingleNode("local")));
30 | tunnelConfig.setRemote(parseEndpoint(tunnel
31 | .selectSingleNode("remote")));
32 | list.add(tunnelConfig);
33 | }
34 |
35 | return list;
36 | }
37 |
38 | private EndpointConfig parseEndpoint(Node node) throws Exception {
39 | EndpointConfig config = new EndpointConfig();
40 | config.setHost(getString(node, "@host", true));
41 | config.setPort(Integer.parseInt(getString(node, "@port", false)));
42 | config.setEnableSSL(Boolean.parseBoolean(getString(node, "@ssl", true)));
43 | return config;
44 | }
45 |
46 | private String getString(Node node, String path, boolean ignore)
47 | throws Exception {
48 | Node n = node.selectSingleNode(path);
49 | if (n == null) {
50 | if (ignore)
51 | return null;
52 | else
53 | throw new Exception("No config node:" + path);
54 | }
55 |
56 | return n.getStringValue();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/EndpointConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | @Setter
7 | @Getter
8 | public class EndpointConfig {
9 | private String host;
10 | private int port;
11 | private boolean enableSSL = false;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/MasterChannelInitializer.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import io.netty.channel.ChannelInitializer;
4 | import io.netty.channel.socket.SocketChannel;
5 | import io.netty.handler.ssl.SslHandler;
6 |
7 | import javax.net.ssl.SSLContext;
8 | import javax.net.ssl.SSLEngine;
9 |
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | @Slf4j
13 | public class MasterChannelInitializer extends ChannelInitializer {
14 | private TunnelConfig config;
15 | private SSLContext sslContext;
16 |
17 | public MasterChannelInitializer(TunnelConfig config) {
18 | this.config = config;
19 | if (config.getLocal().isEnableSSL()) {
20 | sslContext = SSLUtil.getSSLContext();
21 | }
22 | }
23 |
24 | @Override
25 | protected void initChannel(SocketChannel ch) throws Exception {
26 | if (config.getLocal().isEnableSSL()) {
27 | SSLEngine sslEngine = sslContext.createSSLEngine();
28 | sslEngine.setUseClientMode(false);
29 | sslEngine.setNeedClientAuth(true);
30 | ch.pipeline().addLast(new SslHandler(sslEngine));
31 | }
32 | ch.pipeline().addLast(new MasterTransferHandler(config));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/MasterTransferHandler.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import io.netty.bootstrap.Bootstrap;
4 | import io.netty.channel.ChannelFuture;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.ChannelOption;
7 | import io.netty.channel.socket.nio.NioSocketChannel;
8 | import lombok.extern.slf4j.Slf4j;
9 |
10 | @Slf4j
11 | public class MasterTransferHandler extends SlaveTransferHandler {
12 | private TunnelConfig config;
13 |
14 | public MasterTransferHandler(TunnelConfig config) {
15 | this.config = config;
16 | }
17 |
18 | public MasterTransferHandler() {
19 | }
20 |
21 | public TunnelConfig getConfig() {
22 | return config;
23 | }
24 |
25 | public void setConfig(TunnelConfig config) {
26 | this.config = config;
27 | }
28 |
29 | @Override
30 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
31 | log.info("Connection accepted at port {} : {}", config.getLocal()
32 | .getPort(), ctx.channel().remoteAddress());
33 | log.info("Connecting to {}:{}", config.getRemote().getHost(), config
34 | .getRemote().getPort());
35 |
36 | Bootstrap b = new Bootstrap();
37 | b.group(XTunnel.getWorkerGroup());
38 | b.channel(NioSocketChannel.class);
39 | b.option(ChannelOption.SO_KEEPALIVE, true);
40 | b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
41 | b.handler(new SlaveChannelInitializer(config, ctx.channel()));
42 |
43 | // 注意,这里是同步等待,如果网络连接时间过长,会卡住所有线程。
44 | ChannelFuture f = b.connect(config.getRemote().getHost(),
45 | config.getRemote().getPort()).await();
46 | if (f.isSuccess()) {
47 | setRelatedChannel(f.channel());
48 | } else {
49 | log.info("Cannot connect to {}", config.getRemote().getHost(),
50 | config.getRemote().getPort());
51 | ctx.channel().close();
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/SSLUtil.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import java.io.InputStream;
4 | import java.security.KeyStore;
5 | import java.security.SecureRandom;
6 |
7 | import javax.net.ssl.KeyManagerFactory;
8 | import javax.net.ssl.SSLContext;
9 | import javax.net.ssl.TrustManagerFactory;
10 |
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | @Slf4j
14 | public class SSLUtil {
15 | public static SSLContext getSSLContext() {
16 | try {
17 | KeyStore keyStore = KeyStore.getInstance("jks");
18 | InputStream in = SSLUtil.class.getResourceAsStream("/xtunnel.jks");
19 | keyStore.load(in, "123456".toCharArray());
20 | in.close();
21 |
22 | // 初始化key manager factory
23 | KeyManagerFactory kmf = KeyManagerFactory
24 | .getInstance(KeyManagerFactory.getDefaultAlgorithm());
25 | kmf.init(keyStore, "123456".toCharArray());
26 |
27 | TrustManagerFactory tmf = TrustManagerFactory
28 | .getInstance(TrustManagerFactory.getDefaultAlgorithm());
29 | tmf.init(keyStore);
30 |
31 | // 初始化ssl context
32 | SSLContext context = SSLContext.getInstance("TLS");
33 | context.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
34 | new SecureRandom());
35 |
36 | return context;
37 | } catch (Exception e) {
38 | log.error("", e);
39 | return null;
40 | }
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/SlaveChannelInitializer.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelInitializer;
5 | import io.netty.channel.socket.SocketChannel;
6 | import io.netty.handler.ssl.SslHandler;
7 |
8 | import javax.net.ssl.SSLContext;
9 | import javax.net.ssl.SSLEngine;
10 |
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | @Slf4j
14 | public class SlaveChannelInitializer extends ChannelInitializer {
15 | private Channel masterChannel;
16 | private TunnelConfig config;
17 | private SSLContext sslContext;
18 |
19 | public SlaveChannelInitializer(TunnelConfig config, Channel masterChannel) {
20 | this.config = config;
21 | this.masterChannel = masterChannel;
22 | if (config.getRemote().isEnableSSL()) {
23 | sslContext = SSLUtil.getSSLContext();
24 | }
25 | }
26 |
27 | @Override
28 | protected void initChannel(SocketChannel ch) throws Exception {
29 | if (config.getRemote().isEnableSSL()) {
30 | SSLEngine sslEngine = sslContext.createSSLEngine();
31 | sslEngine.setUseClientMode(true);
32 | sslEngine.setNeedClientAuth(false);
33 | ch.pipeline().addLast(new SslHandler(sslEngine));
34 | }
35 | ch.pipeline().addLast(new SlaveTransferHandler(masterChannel));
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/SlaveTransferHandler.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.ChannelInboundHandlerAdapter;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | @Slf4j
10 | public class SlaveTransferHandler extends ChannelInboundHandlerAdapter {
11 | private Channel relatedChannel;
12 |
13 | public SlaveTransferHandler(Channel relatedChannel) {
14 | this.relatedChannel = relatedChannel;
15 | }
16 |
17 | public SlaveTransferHandler() {
18 | }
19 |
20 | public Channel getRelatedChannel() {
21 | return relatedChannel;
22 | }
23 |
24 | public void setRelatedChannel(Channel relatedChannel) {
25 | this.relatedChannel = relatedChannel;
26 | }
27 |
28 | @Override
29 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
30 | log.info("Connection established: {}", ctx.channel().remoteAddress());
31 | }
32 |
33 | @Override
34 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
35 | log.info("Connection closed: {}", ctx.channel().remoteAddress());
36 | if (relatedChannel != null)
37 | relatedChannel.close();
38 | }
39 |
40 | @Override
41 | public void channelRead(ChannelHandlerContext ctx, Object msg)
42 | throws Exception {
43 | if (relatedChannel != null && relatedChannel.isWritable()) {
44 | relatedChannel.writeAndFlush(msg);
45 | }
46 | }
47 |
48 | @Override
49 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
50 | throws Exception {
51 | log.info("Exception occurred: {} : {}", cause.getMessage(), ctx
52 | .channel().remoteAddress());
53 | ctx.channel().close();
54 | if (relatedChannel != null)
55 | relatedChannel.close();
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/SystrayUtil.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import java.awt.AWTException;
4 | import java.awt.Image;
5 | import java.awt.MenuItem;
6 | import java.awt.PopupMenu;
7 | import java.awt.SystemTray;
8 | import java.awt.Toolkit;
9 | import java.awt.TrayIcon;
10 | import java.awt.event.ActionEvent;
11 | import java.awt.event.ActionListener;
12 |
13 | import lombok.extern.slf4j.Slf4j;
14 |
15 | @Slf4j
16 | public class SystrayUtil {
17 | private TrayIcon trayIcon = null;
18 |
19 | public void showSystray() {
20 | // 判断系统是否支持系统托盘
21 | if (!SystemTray.isSupported())
22 | return;
23 |
24 | SystemTray tray = SystemTray.getSystemTray(); // 创建系统托盘
25 | Image image = Toolkit.getDefaultToolkit().getImage(
26 | this.getClass().getResource("/icon.png"));// 载入图片,这里要写你的图标路径哦
27 |
28 | // 创建弹出菜单
29 | PopupMenu popup = new PopupMenu();
30 |
31 | // 退出程序选项
32 | MenuItem exitItem = new MenuItem("Exit");
33 | exitItem.addActionListener(new ActionListener() {
34 | public void actionPerformed(ActionEvent e) {
35 | System.exit(0);
36 | }
37 | });
38 |
39 | popup.add(exitItem);
40 |
41 | trayIcon = new TrayIcon(image, "XTunnel", popup);// 创建trayIcon
42 | trayIcon.setImageAutoSize(true);
43 | try {
44 | tray.add(trayIcon);
45 | } catch (AWTException e1) {
46 | log.error("", e1);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/TunnelConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | @Setter
7 | @Getter
8 | public class TunnelConfig {
9 | private String name;
10 | private EndpointConfig local;
11 | private EndpointConfig remote;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/chinaums/xtunnel/XTunnel.java:
--------------------------------------------------------------------------------
1 | package com.chinaums.xtunnel;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.ChannelFuture;
5 | import io.netty.channel.ChannelOption;
6 | import io.netty.channel.EventLoopGroup;
7 | import io.netty.channel.nio.NioEventLoopGroup;
8 | import io.netty.channel.socket.nio.NioServerSocketChannel;
9 |
10 | import java.awt.GraphicsEnvironment;
11 | import java.io.FileInputStream;
12 | import java.util.List;
13 |
14 | import javax.swing.JOptionPane;
15 | import javax.swing.UIManager;
16 |
17 | import org.slf4j.Logger;
18 | import org.slf4j.LoggerFactory;
19 |
20 | public class XTunnel {
21 | private static Logger log = LoggerFactory.getLogger(XTunnel.class);
22 |
23 | static private EventLoopGroup bossGroup = new NioEventLoopGroup();
24 | static private EventLoopGroup workerGroup = new NioEventLoopGroup(128);
25 | private List configs;
26 | static private boolean isHeadless = GraphicsEnvironment.isHeadless();
27 |
28 | public static void main(String[] args) throws Exception {
29 | if (!isHeadless) {
30 | String lookAndFeel = UIManager.getSystemLookAndFeelClassName();
31 | try {
32 | UIManager.setLookAndFeel(lookAndFeel);
33 | } catch (Exception e) {
34 | }
35 | new SystrayUtil().showSystray();
36 | }
37 |
38 | if (args.length != 1) {
39 | System.out.println("XTunnel ");
40 | if (!isHeadless) {
41 | JOptionPane.showMessageDialog(null,
42 | "未指定配置文件。\nXTunnel ");
43 | }
44 | System.exit(-1);
45 | }
46 |
47 | new XTunnel().start(args[0]);
48 | }
49 |
50 | public XTunnel() {
51 | }
52 |
53 | static public EventLoopGroup getWorkerGroup() {
54 | return workerGroup;
55 | }
56 |
57 | public void start(String configFile) throws Exception {
58 | configs = new ConfigXMLParser().parse(new FileInputStream(configFile));
59 |
60 | for (final TunnelConfig config : configs) {
61 | ServerBootstrap server = new ServerBootstrap();
62 | server.group(bossGroup, workerGroup)
63 | .channel(NioServerSocketChannel.class)
64 | .childHandler(new MasterChannelInitializer(config))
65 | .option(ChannelOption.SO_BACKLOG, 128)
66 | .childOption(ChannelOption.SO_KEEPALIVE, true);
67 | int port = config.getLocal().getPort();
68 | log.info("Bind port: {}", port);
69 | ChannelFuture cf = server.bind(port).await();
70 | if (!cf.isSuccess()) {
71 | log.error("Cannot bind port: {}", port);
72 | if (!isHeadless) {
73 | JOptionPane.showMessageDialog(null, "Cannot bind port: "
74 | + port);
75 | }
76 | System.exit(-1);
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/src/main/java/icon.png
--------------------------------------------------------------------------------
/src/main/java/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} %-5level [%.15thread] %logger{36} - %.-4096msg%n
7 |
8 |
9 |
10 |
12 | xtunnel.log
13 |
14 | xtunnel.log.%d{yyyy-MM-dd}
15 |
16 | 1
17 |
18 |
19 | %d{HH:mm:ss.SSS} %-5level [%.15thread] %logger{36} - %.-4096msg%n
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/xtunnel.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/src/main/java/xtunnel.jks
--------------------------------------------------------------------------------
/src/main/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/src/main/resources/icon.png
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} %-5level [%.15thread] %logger{36} - %.-4096msg%n
7 |
8 |
9 |
10 |
12 | xtunnel.log
13 |
14 | xtunnel.log.%d{yyyy-MM-dd}
15 |
16 | 1
17 |
18 |
19 | %d{HH:mm:ss.SSS} %-5level [%.15thread] %logger{36} - %.-4096msg%n
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/xtunnel.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quhw/xtunnel/1e8138435dcdbd5927419bbf84914e5456daa867/src/main/resources/xtunnel.jks
--------------------------------------------------------------------------------