├── .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 | ![openvpn](https://github.com/quhw/xtunnel/raw/master/openvpn.png) 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 --------------------------------------------------------------------------------