main
chenhaodong 2023-07-05 11:06:03 +08:00
parent 8de47479c5
commit 7c55f8da61
234 changed files with 54941 additions and 18 deletions

19
.gitignore vendored
View File

@ -1,26 +1,13 @@
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
/target
/distribution
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
.*
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

128
README.md
View File

@ -1,3 +1,127 @@
# nwct
lanproxy
--------
内网穿透
[README](README_en.md) | [中文文档](README.md)
lanproxy是一个将局域网个人电脑、服务器代理到公网的内网穿透工具目前仅支持tcp流量转发可支持任何tcp上层协议访问内网网站、本地支付接口调试、ssh访问、远程桌面...。目前市面上提供类似服务的有花生壳、TeamView、GoToMyCloud等等但要使用第三方的公网服务器就必须为第三方付费并且这些服务都有各种各样的限制此外由于数据包会流经第三方因此对数据安全也是一大隐患。
### 相关地址
- 主页 https://lanproxy.thingsglobal.org
- lanproxy-go-client https://github.com/ffay/lanproxy-go-client
- 发布包下载地址 https://github.com/ffay/lanproxy/releases
### 使用
#### 获取发布包
- 拉取源码,运行 mvn package打包后的资源放在distribution目录中包括client和server
- 或直接下载发布包 https://github.com/ffay/lanproxy/releases
#### 配置
##### server配置
server的配置文件放置在conf目录中配置 config.properties
```properties
server.bind=0.0.0.0
#与代理客户端通信端口
server.port=4900
#ssl相关配置
server.ssl.enable=true
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
#这个配置可以忽略
server.ssl.needsClientAuth=false
#WEB在线配置管理相关信息
config.server.bind=0.0.0.0
config.server.port=8090
config.admin.username=admin
config.admin.password=admin
```
代理配置,打开地址 http://ip:8090 ,使用上面配置中配置的用户名密码登录,进入如下代理配置界面
![webconfig](readme_zh_client_list.png)
![webconfig](readme_zh_proxy_list.png)
![webconfig](readme_zh_stat_list.png)
> 一个server可以支持多个客户端连接
> 配置数据存放在 ~/.lanproxy/config.json 文件中
##### Java 客户端配置
> Java client的配置文件放置在conf目录中配置 config.properties
```properties
#与在proxy-server配置后台创建客户端时填写的秘钥保持一致
client.key=
ssl.enable=true
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
#这里填写实际的proxy-server地址没有服务器默认即可自己有服务器的更换为自己的proxy-serverIP地址
server.host=lp.thingsglobal.org
#proxy-server ssl默认端口4993默认普通端口4900
#ssl.enable=true时这里填写ssl端口ssl.enable=false时这里填写普通端口
server.port=4993
```
- 安装java1.7或以上环境
- linuxmac环境中运行bin目录下的 startup.sh
- windows环境中运行bin目录下的 startup.bat
##### 其他平台客户端
> 不用java客户端的可以使用下面提供的各个平台的客户端省去安装java运行环境
###### 源码地址
https://github.com/ffay/lanproxy-go-client
###### 发布包
https://github.com/ffay/lanproxy-go-client/releases
###### 普通端口连接
```shell
# mac 64位
nohup ./client_darwin_amd64 -s SERVER_IP -p SERVER_PORT -k CLIENT_KEY &
# linux 64位
nohup ./client_linux_amd64 -s SERVER_IP -p SERVER_PORT -k CLIENT_KEY &
# windows 64 位
./client_windows_amd64.exe -s SERVER_IP -p SERVER_PORT -k CLIENT_KEY
```
###### SSL端口连接
```shell
# mac 64位
nohup ./client_darwin_amd64 -s SERVER_IP -p SERVER_SSL_PORT -k CLIENT_KEY -ssl true &
# linux 64位
nohup ./client_linux_amd64 -s SERVER_IP -p SERVER_SSL_PORT -k CLIENT_KEY -ssl true &
# windows 64 位
./client_windows_amd64.exe -s SERVER_IP -p SERVER_SSL_PORT -k CLIENT_KEY -ssl true
```
#### 其他
- 后面是将自己电脑从VPS代理出去测试的一个地址大家可以访问试试 http://devbook.thingsglobal.org/
- 对于正常网站80和443端口只有一个可以和 https://github.com/ffay/proxygateway 这个项目或nginx配合使用不同域名反向代理到内部其他端口上去再由其他端口映射到内网即可对于ssh或远程桌面等可随意选择其他未占用的端口映射到内网

84
README_en.md 100644
View File

@ -0,0 +1,84 @@
### Lanproxy
[README](README_en.md) | [中文文档](README.md)
Lanproxy is a reverse proxy to help you expose a local server behind a NAT or firewall to the internet. it supports any protocols over tcp (http https ssh ...)
### Features
- Secure tunnels to localhost
- Supports any protocols over tcp (http https ssh ...)
- Supports web config pages, easy to configure management
- Written by java netty framework, high performance
### What can I do with Lanproxy
- Demo without deploying
- Simplify mobile device testing
- Build webhook integrations with ease
- Run personal cloud services from your own private network
### Architecture
![lanproxy](lanproxy.png)
### Configure
#### Server
proxy-server config file is **conf/config.properties**
```properties
server.bind=0.0.0.0
#Plain tcp port
server.port=4900
#ssl
server.ssl.enable=true
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
#web config pages
config.server.bind=0.0.0.0
config.server.port=8090
config.admin.username=admin
config.admin.password=admin
```
> Visit your config web service using url http://ip:8090
![webconfig](readme_en_client_list.png)
![webconfig](readme_en_proxy_list.png)
![webconfig](readme_en_stat_list.png)
#### client
proxy-client config file is **conf/config.properties**
```properties
#get from proxy-server client list page
client.key=
ssl.enable=true
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
#your proxy server ip
server.host=
#proxy-server ssl port is 4993plain port is 4900
server.port=4993
```
### Run
- Get release package from https://github.com/ffay/lanproxy/releases
- Java env is required
- Linuxmacrun bin/startup.sh
- Windows run bin/startup.bat

BIN
lanproxy.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

68
pom.xml 100644
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.fengfei</groupId>
<artifactId>yxwlkjnwct</artifactId>
<packaging>pom</packaging>
<version>0.1</version>
<name>yxwlkjnwct</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.36.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<modules>
<module>proxy-common</module>
<module>proxy-protocol</module>
<module>proxy-server</module>
<module>proxy-client</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>bat</nonFilteredFileExtension>
<nonFilteredFileExtension>sh</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,129 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fengfei</groupId>
<artifactId>yxwlkjnwct</artifactId>
<version>0.1</version>
</parent>
<artifactId>proxy-client</artifactId>
<packaging>jar</packaging>
<name>proxy-client</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.fengfei</groupId>
<artifactId>proxy-common</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.fengfei</groupId>
<artifactId>proxy-protocol</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
<build>
<finalName>proxy-client-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>../distribution/proxy-client-${project.version}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<excludes>
<exclude>*.properties</exclude>
<exclude>*.sh</exclude>
<exclude>*.bat</exclude>
<exclude>*.jks</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-config</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-client-${project.version}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.properties</include>
<include>*.jks</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-sh</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-client-${project.version}/bin</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.sh</include>
<include>*.bat</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-dist-jar</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-client-${project.version}/lib</outputDirectory>
<resources>
<resource>
<directory>target</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,128 @@
package org.fengfei.lanproxy.client;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.fengfei.lanproxy.client.listener.ProxyChannelBorrowListener;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.protocol.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.util.AttributeKey;
/**
*
*
* @author fengfei
*
*/
public class ClientChannelMannager {
private static Logger logger = LoggerFactory.getLogger(ClientChannelMannager.class);
private static final AttributeKey<Boolean> USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable");
private static final AttributeKey<Boolean> CLIENT_CHANNEL_WRITEABLE = AttributeKey.newInstance("client_channel_writeable");
private static final int MAX_POOL_SIZE = 100;
private static Map<String, Channel> realServerChannels = new ConcurrentHashMap<String, Channel>();
private static ConcurrentLinkedQueue<Channel> proxyChannelPool = new ConcurrentLinkedQueue<Channel>();
private static volatile Channel cmdChannel;
private static Config config = Config.getInstance();
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = proxyChannelPool.poll();
if (channel != null) {
borrowListener.success(channel);
return;
}
bootstrap.connect(config.getStringValue("server.host"), config.getIntValue("server.port")).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
logger.warn("connect proxy server failed", future.cause());
borrowListener.error(future.cause());
}
}
});
}
public static void returnProxyChanel(Channel proxyChanel) {
if (proxyChannelPool.size() > MAX_POOL_SIZE) {
proxyChanel.close();
} else {
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannelPool.offer(proxyChanel);
logger.debug("return ProxyChanel to the pool, channel is {}, pool size is {} ", proxyChanel, proxyChannelPool.size());
}
}
public static void removeProxyChanel(Channel proxyChanel) {
proxyChannelPool.remove(proxyChanel);
}
public static void setCmdChannel(Channel cmdChannel) {
ClientChannelMannager.cmdChannel = cmdChannel;
}
public static Channel getCmdChannel() {
return cmdChannel;
}
public static void setRealServerChannelUserId(Channel realServerChannel, String userId) {
realServerChannel.attr(Constants.USER_ID).set(userId);
}
public static String getRealServerChannelUserId(Channel realServerChannel) {
return realServerChannel.attr(Constants.USER_ID).get();
}
public static Channel getRealServerChannel(String userId) {
return realServerChannels.get(userId);
}
public static void addRealServerChannel(String userId, Channel realServerChannel) {
realServerChannels.put(userId, realServerChannel);
}
public static Channel removeRealServerChannel(String userId) {
return realServerChannels.remove(userId);
}
public static boolean isRealServerReadable(Channel realServerChannel) {
return realServerChannel.attr(CLIENT_CHANNEL_WRITEABLE).get() && realServerChannel.attr(USER_CHANNEL_WRITEABLE).get();
}
public static void clearRealServerChannels() {
logger.warn("channel closed, clear real server channels");
Iterator<Entry<String, Channel>> ite = realServerChannels.entrySet().iterator();
while (ite.hasNext()) {
Channel realServerChannel = ite.next().getValue();
if (realServerChannel.isActive()) {
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
realServerChannels.clear();
}
}

View File

@ -0,0 +1,161 @@
package org.fengfei.lanproxy.client;
import java.util.Arrays;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.fengfei.lanproxy.client.handlers.ClientChannelHandler;
import org.fengfei.lanproxy.client.handlers.RealServerChannelHandler;
import org.fengfei.lanproxy.client.listener.ChannelStatusListener;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.common.container.Container;
import org.fengfei.lanproxy.common.container.ContainerHelper;
import org.fengfei.lanproxy.protocol.IdleCheckHandler;
import org.fengfei.lanproxy.protocol.ProxyMessage;
import org.fengfei.lanproxy.protocol.ProxyMessageDecoder;
import org.fengfei.lanproxy.protocol.ProxyMessageEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
public class ProxyClientContainer implements Container, ChannelStatusListener {
private static Logger logger = LoggerFactory.getLogger(ProxyClientContainer.class);
private static final int MAX_FRAME_LENGTH = 1024 * 1024;
private static final int LENGTH_FIELD_OFFSET = 0;
private static final int LENGTH_FIELD_LENGTH = 4;
private static final int INITIAL_BYTES_TO_STRIP = 0;
private static final int LENGTH_ADJUSTMENT = 0;
private NioEventLoopGroup workerGroup;
private Bootstrap bootstrap;
private Bootstrap realServerBootstrap;
private Config config = Config.getInstance();
private SSLContext sslContext;
private long sleepTimeMill = 1000;
public ProxyClientContainer() {
workerGroup = new NioEventLoopGroup();
realServerBootstrap = new Bootstrap();
realServerBootstrap.group(workerGroup);
realServerBootstrap.channel(NioSocketChannel.class);
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new RealServerChannelHandler());
}
});
bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (Config.getInstance().getBooleanValue("ssl.enable", false)) {
if (sslContext == null) {
sslContext = SslContextCreator.createSSLContext();
}
ch.pipeline().addLast(createSslHandler(sslContext));
}
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME - 10, 0));
ch.pipeline().addLast(new ClientChannelHandler(realServerBootstrap, bootstrap, ProxyClientContainer.this));
}
});
}
@Override
public void start() {
connectProxyServer();
}
private ChannelHandler createSslHandler(SSLContext sslContext) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);
return new SslHandler(sslEngine);
}
private void connectProxyServer() {
bootstrap.connect(config.getStringValue("server.host"), config.getIntValue("server.port")).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 连接成功向服务器发送客户端认证信息clientKey
ClientChannelMannager.setCmdChannel(future.channel());
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.C_TYPE_AUTH);
proxyMessage.setUri(config.getStringValue("client.key"));
future.channel().writeAndFlush(proxyMessage);
sleepTimeMill = 1000;
logger.info("connect proxy server success, {}", future.channel());
} else {
logger.warn("connect proxy server failed", future.cause());
// 连接失败,发起重连
reconnectWait();
connectProxyServer();
}
}
});
}
@Override
public void stop() {
workerGroup.shutdownGracefully();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
reconnectWait();
connectProxyServer();
}
private void reconnectWait() {
try {
if (sleepTimeMill > 60000) {
sleepTimeMill = 1000;
}
synchronized (this) {
sleepTimeMill = sleepTimeMill * 2;
wait(sleepTimeMill);
}
} catch (InterruptedException e) {
}
}
public static void main(String[] args) {
ContainerHelper.start(Arrays.asList(new Container[] { new ProxyClientContainer() }));
}
}

View File

@ -0,0 +1,90 @@
package org.fengfei.lanproxy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.fengfei.lanproxy.common.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SslContextCreator {
private static Logger logger = LoggerFactory.getLogger(SslContextCreator.class);
public static SSLContext createSSLContext() {
return new SslContextCreator().initSSLContext();
}
public SSLContext initSSLContext() {
logger.info("Checking SSL configuration properties...");
final String jksPath = Config.getInstance().getStringValue("ssl.jksPath");
logger.info("Initializing SSL context. KeystorePath = {}.", jksPath);
if (jksPath == null || jksPath.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore path is null or empty. The SSL context won't be initialized.");
return null;
}
// if we have the port also the jks then keyStorePassword and
// keyManagerPassword
// has to be defined
final String keyStorePassword = Config.getInstance().getStringValue("ssl.keyStorePassword");
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
try {
logger.info("Loading keystore. KeystorePath = {}.", jksPath);
InputStream jksInputStream = jksDatastore(jksPath);
SSLContext clientSSLContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
// init sslContext
logger.info("Initializing SSL context...");
clientSSLContext.init(null, trustManagers, null);
logger.info("The SSL context has been initialized successfully.");
return clientSSLContext;
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException
| IOException ex) {
logger.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(),
ex.getMessage());
return null;
}
}
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
if (jksUrl != null) {
logger.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
return getClass().getClassLoader().getResourceAsStream(jksPath);
}
logger.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
File jksFile = new File(jksPath);
if (jksFile.exists()) {
logger.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
return new FileInputStream(jksFile);
}
logger.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath());
return null;
}
}

View File

@ -0,0 +1,174 @@
package org.fengfei.lanproxy.client.handlers;
import org.fengfei.lanproxy.client.ClientChannelMannager;
import org.fengfei.lanproxy.client.listener.ChannelStatusListener;
import org.fengfei.lanproxy.client.listener.ProxyChannelBorrowListener;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.protocol.Constants;
import org.fengfei.lanproxy.protocol.ProxyMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
/**
*
* @author fengfei
*
*/
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static Logger logger = LoggerFactory.getLogger(ClientChannelHandler.class);
private Bootstrap bootstrap;
private Bootstrap proxyBootstrap;
private ChannelStatusListener channelStatusListener;
public ClientChannelHandler(Bootstrap bootstrap, Bootstrap proxyBootstrap, ChannelStatusListener channelStatusListener) {
this.bootstrap = bootstrap;
this.proxyBootstrap = proxyBootstrap;
this.channelStatusListener = channelStatusListener;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
logger.debug("recieved proxy message, type is {}", proxyMessage.getType());
switch (proxyMessage.getType()) {
case ProxyMessage.TYPE_CONNECT:
handleConnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_DISCONNECT:
handleDisconnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.P_TYPE_TRANSFER:
handleTransferMessage(ctx, proxyMessage);
break;
default:
break;
}
}
private void handleTransferMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
logger.debug("write data to real server, {}", realServerChannel);
realServerChannel.writeAndFlush(buf);
}
}
private void handleDisconnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
logger.debug("handleDisconnectMessage, {}", realServerChannel);
if (realServerChannel != null) {
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
ClientChannelMannager.returnProxyChanel(ctx.channel());
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
private void handleConnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final Channel cmdChannel = ctx.channel();
final String userId = proxyMessage.getUri();
String[] serverInfo = new String(proxyMessage.getData()).split(":");
String ip = serverInfo[0];
int port = Integer.parseInt(serverInfo[1]);
bootstrap.connect(ip, port).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 连接后端服务器成功
if (future.isSuccess()) {
final Channel realServerChannel = future.channel();
logger.debug("connect realserver success, {}", realServerChannel);
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 获取连接
ClientChannelMannager.borrowProxyChanel(proxyBootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
// 连接绑定
channel.attr(Constants.NEXT_CHANNEL).set(realServerChannel);
realServerChannel.attr(Constants.NEXT_CHANNEL).set(channel);
// 远程绑定
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_CONNECT);
proxyMessage.setUri(userId + "@" + Config.getInstance().getStringValue("client.key"));
channel.writeAndFlush(proxyMessage);
realServerChannel.config().setOption(ChannelOption.AUTO_READ, true);
ClientChannelMannager.addRealServerChannel(userId, realServerChannel);
ClientChannelMannager.setRealServerChannelUserId(realServerChannel, userId);
}
@Override
public void error(Throwable cause) {
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
proxyMessage.setUri(userId);
cmdChannel.writeAndFlush(proxyMessage);
}
});
} else {
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
proxyMessage.setUri(userId);
cmdChannel.writeAndFlush(proxyMessage);
}
}
});
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 控制连接
if (ClientChannelMannager.getCmdChannel() == ctx.channel()) {
ClientChannelMannager.setCmdChannel(null);
ClientChannelMannager.clearRealServerChannels();
channelStatusListener.channelInactive(ctx);
} else {
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
}
ClientChannelMannager.removeProxyChanel(ctx.channel());
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("exception caught", cause);
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,80 @@
package org.fengfei.lanproxy.client.handlers;
import org.fengfei.lanproxy.client.ClientChannelMannager;
import org.fengfei.lanproxy.protocol.Constants;
import org.fengfei.lanproxy.protocol.ProxyMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* channel.
*/
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static Logger logger = LoggerFactory.getLogger(RealServerChannelHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
Channel realServerChannel = ctx.channel();
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (channel == null) {
// 代理客户端连接断开
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String userId = ClientChannelMannager.getRealServerChannelUserId(realServerChannel);
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.P_TYPE_TRANSFER);
proxyMessage.setUri(userId);
proxyMessage.setData(bytes);
channel.writeAndFlush(proxyMessage);
logger.debug("write data to proxy server, {}, {}", realServerChannel, channel);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel();
String userId = ClientChannelMannager.getRealServerChannelUserId(realServerChannel);
ClientChannelMannager.removeRealServerChannel(userId);
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (channel != null) {
logger.debug("channelInactive, {}", realServerChannel);
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
proxyMessage.setUri(userId);
channel.writeAndFlush(proxyMessage);
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel();
Channel proxyChannel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, realServerChannel.isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("exception caught", cause);
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,9 @@
package org.fengfei.lanproxy.client.listener;
import io.netty.channel.ChannelHandlerContext;
public interface ChannelStatusListener {
void channelInactive(ChannelHandlerContext ctx);
}

View File

@ -0,0 +1,11 @@
package org.fengfei.lanproxy.client.listener;
import io.netty.channel.Channel;
public interface ProxyChannelBorrowListener {
void success(Channel channel);
void error(Throwable cause);
}

View File

@ -0,0 +1,11 @@
#由服务端生成的key
client.key=client
#ssl是否开启配置应与服务端一致
ssl.enable=false
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
#服务端的ip
server.host=127.0.0.1
#服务端的端口
#default ssl port is 4993
server.port=4900

View File

@ -0,0 +1,13 @@
log4j.rootLogger=info,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.R.File=${app.home}/logs/client.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn

View File

@ -0,0 +1,11 @@
@echo off & setlocal enabledelayedexpansion
title lanproxy-client
cd %~dp0
set LIB_JARS=""
cd ..\lib
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
cd ..\bin
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.client.ProxyClientContainer
goto end

View File

@ -0,0 +1,43 @@
#!/bin/bash
cd `dirname $0`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf
LOGS_DIR=$DEPLOY_DIR/logs
APP_MAINCLASS=org.fengfei.lanproxy.client.ProxyClientContainer
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -n "$PIDS" ]; then
echo "ERROR: already started!"
echo "PID: $PIDS"
exit 1
fi
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
CLOG_FILE=$LOGS_DIR/gc.log
LIB_DIR=$DEPLOY_DIR/lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
fi
JAVA_MEM_OPTS=""
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
echo -e "Starting the proxy client ...\c"
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
sleep 1
echo "started"
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"

View File

@ -0,0 +1,35 @@
#!/bin/bash
cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
LOGS_DIR=$DEPLOY_DIR/logs
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
echo "PID: $PID"
if [ -z "$PID" ]; then
echo "ERROR: The proxy client does not started!"
exit 1
fi
echo -e "Stopping the proxy client...\c"
kill $PID > $STDOUT_FILE 2>&1
COUNT=0
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
COUNT=1
PID_EXIST=`ps -f -p $PID | grep java`
if [ -n "$PID_EXIST" ]; then
COUNT=0
fi
done
echo "stopped"
echo "PID: $PID"

Binary file not shown.

View File

@ -0,0 +1,15 @@
package org.fengfei.lanparoxy.client.test;
import java.util.Arrays;
import org.fengfei.lanproxy.client.ProxyClientContainer;
import org.fengfei.lanproxy.common.container.Container;
import org.fengfei.lanproxy.common.container.ContainerHelper;
public class TestMain {
public static void main(String[] args) {
ContainerHelper.start(Arrays.asList(new Container[] { new ProxyClientContainer() }));
}
}

View File

@ -0,0 +1,9 @@
client.key=f49bfbca84e646a5ba4ce51ad9d8c14e
ssl.enable=false
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
server.host=127.0.0.1
#default ssl port is 4993
server.port=4900

View File

@ -0,0 +1,9 @@
log4j.rootLogger=debug,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn
log4j.logger.org.fengfei.lanproxy.client.handlers.ClientChannelHandler=info
log4j.logger.org.fengfei.lanproxy.protocol.IdleCheckHandler=warn

Binary file not shown.

View File

@ -0,0 +1,11 @@
#由服务端生成的key
client.key=client
#ssl是否开启配置应与服务端一致
ssl.enable=false
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
#服务端的ip
server.host=127.0.0.1
#服务端的端口
#default ssl port is 4993
server.port=4900

View File

@ -0,0 +1,13 @@
log4j.rootLogger=info,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.R.File=${app.home}/logs/client.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn

View File

@ -0,0 +1,11 @@
@echo off & setlocal enabledelayedexpansion
title lanproxy-client
cd %~dp0
set LIB_JARS=""
cd ..\lib
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
cd ..\bin
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.client.ProxyClientContainer
goto end

View File

@ -0,0 +1,43 @@
#!/bin/bash
cd `dirname $0`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf
LOGS_DIR=$DEPLOY_DIR/logs
APP_MAINCLASS=org.fengfei.lanproxy.client.ProxyClientContainer
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -n "$PIDS" ]; then
echo "ERROR: already started!"
echo "PID: $PIDS"
exit 1
fi
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
CLOG_FILE=$LOGS_DIR/gc.log
LIB_DIR=$DEPLOY_DIR/lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
fi
JAVA_MEM_OPTS=""
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
echo -e "Starting the proxy client ...\c"
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
sleep 1
echo "started"
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"

View File

@ -0,0 +1,35 @@
#!/bin/bash
cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
LOGS_DIR=$DEPLOY_DIR/logs
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
echo "PID: $PID"
if [ -z "$PID" ]; then
echo "ERROR: The proxy client does not started!"
exit 1
fi
echo -e "Stopping the proxy client...\c"
kill $PID > $STDOUT_FILE 2>&1
COUNT=0
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
COUNT=1
PID_EXIST=`ps -f -p $PID | grep java`
if [ -n "$PID_EXIST" ]; then
COUNT=0
fi
done
echo "stopped"
echo "PID: $PID"

Binary file not shown.

View File

@ -0,0 +1,5 @@
#Generated by Maven
#Wed Jul 05 11:03:49 CST 2023
groupId=org.fengfei
artifactId=proxy-client
version=0.1

View File

@ -0,0 +1,13 @@
org\fengfei\lanproxy\client\handlers\ClientChannelHandler$1$1.class
org\fengfei\lanproxy\client\handlers\ClientChannelHandler$1.class
org\fengfei\lanproxy\client\ClientChannelMannager.class
org\fengfei\lanproxy\client\ProxyClientContainer.class
org\fengfei\lanproxy\client\ClientChannelMannager$1.class
org\fengfei\lanproxy\client\handlers\RealServerChannelHandler.class
org\fengfei\lanproxy\client\listener\ChannelStatusListener.class
org\fengfei\lanproxy\client\SslContextCreator.class
org\fengfei\lanproxy\client\ProxyClientContainer$3.class
org\fengfei\lanproxy\client\handlers\ClientChannelHandler.class
org\fengfei\lanproxy\client\listener\ProxyChannelBorrowListener.class
org\fengfei\lanproxy\client\ProxyClientContainer$1.class
org\fengfei\lanproxy\client\ProxyClientContainer$2.class

View File

@ -0,0 +1,7 @@
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\ProxyClientContainer.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\handlers\ClientChannelHandler.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\listener\ProxyChannelBorrowListener.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\listener\ChannelStatusListener.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\SslContextCreator.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\ClientChannelMannager.java
E:\yx\lanproxy-0.1\proxy-client\src\main\java\org\fengfei\lanproxy\client\handlers\RealServerChannelHandler.java

View File

@ -0,0 +1 @@
org\fengfei\lanparoxy\client\test\TestMain.class

View File

@ -0,0 +1 @@
E:\yx\lanproxy-0.1\proxy-client\src\test\java\org\fengfei\lanparoxy\client\test\TestMain.java

View File

@ -0,0 +1,9 @@
client.key=f49bfbca84e646a5ba4ce51ad9d8c14e
ssl.enable=false
ssl.jksPath=test.jks
ssl.keyStorePassword=123456
server.host=127.0.0.1
#default ssl port is 4993
server.port=4900

View File

@ -0,0 +1,9 @@
log4j.rootLogger=debug,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn
log4j.logger.org.fengfei.lanproxy.client.handlers.ClientChannelHandler=info
log4j.logger.org.fengfei.lanproxy.protocol.IdleCheckHandler=warn

Binary file not shown.

View File

@ -0,0 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fengfei</groupId>
<artifactId>yxwlkjnwct</artifactId>
<version>0.1</version>
</parent>
<artifactId>proxy-common</artifactId>
<packaging>jar</packaging>
<name>proxy-common</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,120 @@
package org.fengfei.lanproxy.common;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* config.properties
*
*/
public class Config {
private static final String DEFAULT_CONF = "config.properties";
private static Map<String, Config> instances = new ConcurrentHashMap<String, Config>();
private Properties configuration = new Properties();
private Config() {
initConfig(DEFAULT_CONF);
}
private Config(String configFile) {
initConfig(configFile);
}
private void initConfig(String configFile) {
InputStream is = Config.class.getClassLoader().getResourceAsStream(configFile);
try {
configuration.load(is);
is.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Configuration config.property
*
* @return Configuration
*/
public static Config getInstance() {
return getInstance(DEFAULT_CONF);
}
/**
* **.property
*
* @param configFile
* @return
*/
public static Config getInstance(String configFile) {
Config config = instances.get(configFile);
if (config == null) {
synchronized (instances) {
config = instances.get(configFile);
if (config == null) {
config = new Config(configFile);
instances.put(configFile, config);
}
}
}
return config;
}
/**
*
*
* @param key
*
* @return
*/
public String getStringValue(String key) {
return configuration.getProperty(key);
}
public String getStringValue(String key, String defaultValue) {
String value = this.getStringValue(key);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public int getIntValue(String key, int defaultValue) {
return LangUtil.parseInt(configuration.getProperty(key), defaultValue);
}
public int getIntValue(String key) {
return LangUtil.parseInt(configuration.getProperty(key));
}
public double getDoubleValue(String key, Double defaultValue) {
return LangUtil.parseDouble(configuration.getProperty(key), defaultValue);
}
public double getDoubleValue(String key) {
return LangUtil.parseDouble(configuration.getProperty(key));
}
public double getLongValue(String key, Long defaultValue) {
return LangUtil.parseLong(configuration.getProperty(key), defaultValue);
}
public double getLongValue(String key) {
return LangUtil.parseLong(configuration.getProperty(key));
}
public Boolean getBooleanValue(String key, Boolean defaultValue) {
return LangUtil.parseBoolean(configuration.getProperty(key), defaultValue);
}
public Boolean getBooleanValue(String key) {
return LangUtil.parseBoolean(configuration.getProperty(key));
}
}

View File

@ -0,0 +1,34 @@
package org.fengfei.lanproxy.common;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
* JSONPOJO.
*
* @author fengfei
*
*/
public class JsonUtil {
@SuppressWarnings("unchecked")
public static <T> T json2object(String json, TypeToken<T> typeToken) {
try {
Gson gson = new Gson();
return (T) gson.fromJson(json, typeToken.getType());
} catch (Exception e) {
}
return null;
}
/**
*
* javajson
*
*/
public static String object2json(Object obj) {
Gson gson = new Gson();
return gson.toJson(obj);
}
}

View File

@ -0,0 +1,146 @@
package org.fengfei.lanproxy.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* .
*
* @author fengfei
*
*/
public class LangUtil {
private static Logger logger = LoggerFactory.getLogger(LangUtil.class);
public static Boolean parseBoolean(Object value) {
if (value != null) {
if (value instanceof Boolean) {
return (Boolean) value;
} else if (value instanceof String) {
return Boolean.valueOf((String) value);
}
}
return null;
}
public static boolean parseBoolean(Object value, boolean defaultValue) {
if (value != null) {
if (value instanceof Boolean) {
return (Boolean) value;
} else if (value instanceof String) {
try {
return Boolean.valueOf((String) value);
} catch (Exception e) {
logger.warn("parse boolean value({}) failed.", value);
}
}
}
return defaultValue;
}
/**
* @Title: parseInt
* @Description: IntIntegerString
* @param value IntegerString
* @return Integer
*/
public static Integer parseInt(Object value) {
if (value != null) {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof String) {
return Integer.valueOf((String) value);
}
}
return null;
}
public static Integer parseInt(Object value, Integer defaultValue) {
if (value != null) {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof String) {
try {
return Integer.valueOf((String) value);
} catch (Exception e) {
logger.warn("parse Integer value({}) failed.", value);
}
}
}
return defaultValue;
}
/***
*
* @Title: parseLong
* @Description: longLongString
* @param value IntegerString
* @param @return
* @return Long
*/
public static Long parseLong(Object value) {
if (value != null) {
if (value instanceof Long) {
return (Long) value;
} else if (value instanceof String) {
return Long.valueOf((String) value);
}
}
return null;
}
public static Long parseLong(Object value, Long defaultValue) {
if (value != null) {
if (value instanceof Long) {
return (Long) value;
} else if (value instanceof String) {
try {
return Long.valueOf((String) value);
} catch (NumberFormatException e) {
logger.warn("parse Long value({}) failed.", value);
}
}
}
return defaultValue;
}
/**
* @Title: parseDouble
* @Description: DoubleDoubleString
* @param value DoubleString
* @return Double
*/
public static Double parseDouble(Object value) {
if (value != null) {
if (value instanceof Double) {
return (Double) value;
} else if (value instanceof String) {
return Double.valueOf((String) value);
}
}
return null;
}
/**
* @Title: parseDouble
* @Description: DoubleDoubleString
* @param value DoubleString
* @return Double
*/
public static Double parseDouble(Object value, Double defaultValue) {
if (value != null) {
if (value instanceof Double) {
return (Double) value;
} else if (value instanceof String) {
try {
return Double.valueOf((String) value);
} catch (NumberFormatException e) {
logger.warn("parse Double value({}) failed.", value);
}
}
}
return defaultValue;
}
}

View File

@ -0,0 +1,8 @@
package org.fengfei.lanproxy.common.container;
public interface Container {
void start();
void stop();
}

View File

@ -0,0 +1,73 @@
package org.fengfei.lanproxy.common.container;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* .
*
* @author fengfei
*
*/
public class ContainerHelper {
private static Logger logger = LoggerFactory.getLogger(ContainerHelper.class);
private static volatile boolean running = true;
private static List<Container> cachedContainers;
public static void start(List<Container> containers) {
cachedContainers = containers;
// 启动所有容器
startContainers();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
synchronized (ContainerHelper.class) {
// 停止所有容器.
stopContainers();
running = false;
ContainerHelper.class.notify();
}
}
});
synchronized (ContainerHelper.class) {
while (running) {
try {
ContainerHelper.class.wait();
} catch (Throwable e) {
}
}
}
}
private static void startContainers() {
for (Container container : cachedContainers) {
logger.info("starting container [{}]", container.getClass().getName());
container.start();
logger.info("container [{}] started", container.getClass().getName());
}
}
private static void stopContainers() {
for (Container container : cachedContainers) {
logger.info("stopping container [{}]", container.getClass().getName());
try {
container.stop();
logger.info("container [{}] stopped", container.getClass().getName());
} catch (Exception ex) {
logger.warn("container stopped with error", ex);
}
}
}
}

View File

@ -0,0 +1,5 @@
#Generated by Maven
#Wed Jul 05 11:03:47 CST 2023
groupId=org.fengfei
artifactId=proxy-common
version=0.1

View File

@ -0,0 +1,6 @@
org\fengfei\lanproxy\common\container\Container.class
org\fengfei\lanproxy\common\JsonUtil.class
org\fengfei\lanproxy\common\LangUtil.class
org\fengfei\lanproxy\common\container\ContainerHelper$1.class
org\fengfei\lanproxy\common\Config.class
org\fengfei\lanproxy\common\container\ContainerHelper.class

View File

@ -0,0 +1,5 @@
E:\yx\lanproxy-0.1\proxy-common\src\main\java\org\fengfei\lanproxy\common\LangUtil.java
E:\yx\lanproxy-0.1\proxy-common\src\main\java\org\fengfei\lanproxy\common\container\Container.java
E:\yx\lanproxy-0.1\proxy-common\src\main\java\org\fengfei\lanproxy\common\container\ContainerHelper.java
E:\yx\lanproxy-0.1\proxy-common\src\main\java\org\fengfei\lanproxy\common\Config.java
E:\yx\lanproxy-0.1\proxy-common\src\main\java\org\fengfei\lanproxy\common\JsonUtil.java

View File

@ -0,0 +1,13 @@
package org.fengfei.lanproxy.server;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
public interface Constants {
public static final AttributeKey<Channel> NEXT_CHANNEL = AttributeKey.newInstance("nxt_channel");
public static final AttributeKey<String> USER_ID = AttributeKey.newInstance("user_id");
public static final AttributeKey<String> CLIENT_KEY = AttributeKey.newInstance("client_key");
}

View File

@ -0,0 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fengfei</groupId>
<artifactId>yxwlkjnwct</artifactId>
<version>0.1</version>
</parent>
<artifactId>proxy-protocol</artifactId>
<packaging>jar</packaging>
<name>proxy-protocol</name>
<url>http://maven.apache.org</url>
<dependencies>
</dependencies>
</project>

View File

@ -0,0 +1,13 @@
package org.fengfei.lanproxy.protocol;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
public interface Constants {
public static final AttributeKey<Channel> NEXT_CHANNEL = AttributeKey.newInstance("nxt_channel");
public static final AttributeKey<String> USER_ID = AttributeKey.newInstance("user_id");
public static final AttributeKey<String> CLIENT_KEY = AttributeKey.newInstance("client_key");
}

View File

@ -0,0 +1,44 @@
package org.fengfei.lanproxy.protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
/**
* check idle chanel.
*
* @author fengfei
*
*/
public class IdleCheckHandler extends IdleStateHandler {
public static final int USER_CHANNEL_READ_IDLE_TIME = 1200;
public static final int READ_IDLE_TIME = 60;
public static final int WRITE_IDLE_TIME = 40;
private static Logger logger = LoggerFactory.getLogger(IdleCheckHandler.class);
public IdleCheckHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
super(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds);
}
@Override
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
if (IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT == evt) {
logger.debug("channel write timeout {}", ctx.channel());
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_HEARTBEAT);
ctx.channel().writeAndFlush(proxyMessage);
} else if (IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT == evt) {
logger.warn("channel read timeout {}", ctx.channel());
ctx.channel().close();
}
super.channelIdle(ctx, evt);
}
}

View File

@ -0,0 +1,83 @@
package org.fengfei.lanproxy.protocol;
import java.util.Arrays;
/**
*
*
* @author fengfei
*
*/
public class ProxyMessage {
/** 心跳消息 */
public static final byte TYPE_HEARTBEAT = 0x07;
/** 认证消息检测clientKey是否正确 */
public static final byte C_TYPE_AUTH = 0x01;
// /** 保活确认消息 */
// public static final byte TYPE_ACK = 0x02;
/** 代理后端服务器建立连接消息 */
public static final byte TYPE_CONNECT = 0x03;
/** 代理后端服务器断开连接消息 */
public static final byte TYPE_DISCONNECT = 0x04;
/** 代理数据传输 */
public static final byte P_TYPE_TRANSFER = 0x05;
/** 用户与代理服务器以及代理客户端与真实服务器连接是否可写状态同步 */
public static final byte C_TYPE_WRITE_CONTROL = 0x06;
/** 消息类型 */
private byte type;
/** 消息流水号 */
private long serialNumber;
/** 消息命令请求信息 */
private String uri;
/** 消息传输数据 */
private byte[] data;
public void setUri(String uri) {
this.uri = uri;
}
public String getUri() {
return uri;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public long getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(long serialNumber) {
this.serialNumber = serialNumber;
}
@Override
public String toString() {
return "ProxyMessage [type=" + type + ", serialNumber=" + serialNumber + ", uri=" + uri + ", data=" + Arrays.toString(data) + "]";
}
}

View File

@ -0,0 +1,78 @@
package org.fengfei.lanproxy.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class ProxyMessageDecoder extends LengthFieldBasedFrameDecoder {
private static final byte HEADER_SIZE = 4;
private static final int TYPE_SIZE = 1;
private static final int SERIAL_NUMBER_SIZE = 8;
private static final int URI_LENGTH_SIZE = 1;
/**
* @param maxFrameLength
* @param lengthFieldOffset
* @param lengthFieldLength
* @param lengthAdjustment
* @param initialBytesToStrip
*/
public ProxyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
/**
* @param maxFrameLength
* @param lengthFieldOffset
* @param lengthFieldLength
* @param lengthAdjustment
* @param initialBytesToStrip
* @param failFast
*/
public ProxyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
@Override
protected ProxyMessage decode(ChannelHandlerContext ctx, ByteBuf in2) throws Exception {
ByteBuf in = (ByteBuf) super.decode(ctx, in2);
if (in == null) {
return null;
}
if (in.readableBytes() < HEADER_SIZE) {
return null;
}
int frameLength = in.readInt();
if (in.readableBytes() < frameLength) {
return null;
}
ProxyMessage proxyMessage = new ProxyMessage();
byte type = in.readByte();
long sn = in.readLong();
proxyMessage.setSerialNumber(sn);
proxyMessage.setType(type);
byte uriLength = in.readByte();
byte[] uriBytes = new byte[uriLength];
in.readBytes(uriBytes);
proxyMessage.setUri(new String(uriBytes));
byte[] data = new byte[frameLength - TYPE_SIZE - SERIAL_NUMBER_SIZE - URI_LENGTH_SIZE - uriLength];
in.readBytes(data);
proxyMessage.setData(data);
in.release();
return proxyMessage;
}
}

View File

@ -0,0 +1,45 @@
package org.fengfei.lanproxy.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class ProxyMessageEncoder extends MessageToByteEncoder<ProxyMessage> {
private static final int TYPE_SIZE = 1;
private static final int SERIAL_NUMBER_SIZE = 8;
private static final int URI_LENGTH_SIZE = 1;
@Override
protected void encode(ChannelHandlerContext ctx, ProxyMessage msg, ByteBuf out) throws Exception {
int bodyLength = TYPE_SIZE + SERIAL_NUMBER_SIZE + URI_LENGTH_SIZE;
byte[] uriBytes = null;
if (msg.getUri() != null) {
uriBytes = msg.getUri().getBytes();
bodyLength += uriBytes.length;
}
if (msg.getData() != null) {
bodyLength += msg.getData().length;
}
// write the total packet length but without length field's length.
out.writeInt(bodyLength);
out.writeByte(msg.getType());
out.writeLong(msg.getSerialNumber());
if (uriBytes != null) {
out.writeByte((byte) uriBytes.length);
out.writeBytes(uriBytes);
} else {
out.writeByte((byte) 0x00);
}
if (msg.getData() != null) {
out.writeBytes(msg.getData());
}
}
}

View File

@ -0,0 +1,5 @@
#Generated by Maven
#Wed Jul 05 11:03:47 CST 2023
groupId=org.fengfei
artifactId=proxy-protocol
version=0.1

View File

@ -0,0 +1,5 @@
org\fengfei\lanproxy\protocol\Constants.class
org\fengfei\lanproxy\protocol\ProxyMessage.class
org\fengfei\lanproxy\protocol\IdleCheckHandler.class
org\fengfei\lanproxy\protocol\ProxyMessageEncoder.class
org\fengfei\lanproxy\protocol\ProxyMessageDecoder.class

View File

@ -0,0 +1,5 @@
E:\yx\lanproxy-0.1\proxy-protocol\src\main\java\org\fengfei\lanproxy\protocol\Constants.java
E:\yx\lanproxy-0.1\proxy-protocol\src\main\java\org\fengfei\lanproxy\protocol\ProxyMessageEncoder.java
E:\yx\lanproxy-0.1\proxy-protocol\src\main\java\org\fengfei\lanproxy\protocol\ProxyMessageDecoder.java
E:\yx\lanproxy-0.1\proxy-protocol\src\main\java\org\fengfei\lanproxy\protocol\ProxyMessage.java
E:\yx\lanproxy-0.1\proxy-protocol\src\main\java\org\fengfei\lanproxy\protocol\IdleCheckHandler.java

View File

@ -0,0 +1,146 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fengfei</groupId>
<artifactId>yxwlkjnwct</artifactId>
<version>0.1</version>
</parent>
<artifactId>proxy-server</artifactId>
<packaging>jar</packaging>
<name>proxy-server</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.fengfei</groupId>
<artifactId>proxy-common</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.fengfei</groupId>
<artifactId>proxy-protocol</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
<build>
<finalName>proxy-server-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<excludes>
<exclude>*.properties</exclude>
<exclude>*.sh</exclude>
<exclude>*.bat</exclude>
<exclude>*.jks</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>../distribution/proxy-server-${project.version}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-config</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-server-${project.version}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.properties</include>
<include>*.json</include>
<include>*.jks</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-webpages</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-server-${project.version}/webpages</outputDirectory>
<resources>
<resource>
<directory>webpages</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-sh</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-server-${project.version}/bin</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.sh</include>
<include>*.bat</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-dist-jar</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>../distribution/proxy-server-${project.version}/lib</outputDirectory>
<resources>
<resource>
<directory>target</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,292 @@
package org.fengfei.lanproxy.server;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.fengfei.lanproxy.protocol.Constants;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import org.fengfei.lanproxy.server.config.ProxyConfig.ConfigChangedListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
/**
* +
*
* @author fengfei
*
*/
public class ProxyChannelManager {
private static Logger logger = LoggerFactory.getLogger(ProxyChannelManager.class);
private static final AttributeKey<Map<String, Channel>> USER_CHANNELS = AttributeKey.newInstance("user_channels");
private static final AttributeKey<String> REQUEST_LAN_INFO = AttributeKey.newInstance("request_lan_info");
private static final AttributeKey<List<Integer>> CHANNEL_PORT = AttributeKey.newInstance("channel_port");
private static final AttributeKey<String> CHANNEL_CLIENT_KEY = AttributeKey.newInstance("channel_client_key");
private static Map<Integer, Channel> portCmdChannelMapping = new ConcurrentHashMap<Integer, Channel>();
private static Map<String, Channel> cmdChannels = new ConcurrentHashMap<String, Channel>();
static {
ProxyConfig.getInstance().addConfigChangedListener(new ConfigChangedListener() {
/**
*
*/
@Override
public synchronized void onChanged() {
Iterator<Entry<String, Channel>> ite = cmdChannels.entrySet().iterator();
while (ite.hasNext()) {
Channel proxyChannel = ite.next().getValue();
String clientKey = proxyChannel.attr(CHANNEL_CLIENT_KEY).get();
// 去除已经去掉的clientKey配置
Set<String> clientKeySet = ProxyConfig.getInstance().getClientKeySet();
if (!clientKeySet.contains(clientKey)) {
removeCmdChannel(proxyChannel);
continue;
}
if (proxyChannel.isActive()) {
List<Integer> inetPorts = new ArrayList<Integer>(ProxyConfig.getInstance().getClientInetPorts(clientKey));
Set<Integer> inetPortSet = new HashSet<Integer>(inetPorts);
List<Integer> channelInetPorts = new ArrayList<Integer>(proxyChannel.attr(CHANNEL_PORT).get());
synchronized (portCmdChannelMapping) {
// 移除旧的连接映射关系
for (int chanelInetPort : channelInetPorts) {
Channel channel = portCmdChannelMapping.get(chanelInetPort);
if (channel == null) {
continue;
}
// 判断是否是同一个连接对象有可能之前已经更换成其他client的连接了
if (proxyChannel == channel) {
if (!inetPortSet.contains(chanelInetPort)) {
// 移除新配置中不包含的端口
portCmdChannelMapping.remove(chanelInetPort);
proxyChannel.attr(CHANNEL_PORT).get().remove(new Integer(chanelInetPort));
} else {
// 端口已经在改proxyChannel中使用了
inetPorts.remove(new Integer(chanelInetPort));
}
}
}
// 将新配置中增加的外网端口写入到映射配置中
for (int inetPort : inetPorts) {
portCmdChannelMapping.put(inetPort, proxyChannel);
proxyChannel.attr(CHANNEL_PORT).get().add(inetPort);
}
checkAndClearUserChannels(proxyChannel);
}
}
}
ite = cmdChannels.entrySet().iterator();
while (ite.hasNext()) {
Entry<String, Channel> entry = ite.next();
Channel proxyChannel = entry.getValue();
logger.info("proxyChannel config, {}, {}, {} ,{}", entry.getKey(), proxyChannel, getUserChannels(proxyChannel).size(), proxyChannel.attr(CHANNEL_PORT).get());
}
}
/**
*
*
* @param proxyChannel
*/
private void checkAndClearUserChannels(Channel proxyChannel) {
Map<String, Channel> userChannels = getUserChannels(proxyChannel);
Iterator<Entry<String, Channel>> userChannelIte = userChannels.entrySet().iterator();
while (userChannelIte.hasNext()) {
Entry<String, Channel> entry = userChannelIte.next();
Channel userChannel = entry.getValue();
String requestLanInfo = getUserChannelRequestLanInfo(userChannel);
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
// 判断当前配置中对应外网端口的lan信息是否与正在运行的连接中的lan信息是否一致
if (lanInfo == null || !lanInfo.equals(requestLanInfo)) {
userChannel.close();
// ConcurrentHashMap不会报ConcurrentModificationException异常
userChannels.remove(entry.getKey());
}
}
}
});
}
/**
*
*
* @param ports
* @param channel
*/
public static void addCmdChannel(List<Integer> ports, String clientKey, Channel channel) {
if (ports == null) {
throw new IllegalArgumentException("port can not be null");
}
// 客户端proxy-client相对较少这里同步的比较重
// 保证服务器对外端口与客户端到服务器的连接关系在临界情况时调用removeChannel(Channel channel)时不出问题
synchronized (portCmdChannelMapping) {
for (int port : ports) {
portCmdChannelMapping.put(port, channel);
}
}
channel.attr(CHANNEL_PORT).set(ports);
channel.attr(CHANNEL_CLIENT_KEY).set(clientKey);
channel.attr(USER_CHANNELS).set(new ConcurrentHashMap<String, Channel>());
cmdChannels.put(clientKey, channel);
}
/**
*
*
* @param channel
*/
public static void removeCmdChannel(Channel channel) {
logger.warn("channel closed, clear user channels, {}", channel);
if (channel.attr(CHANNEL_PORT).get() == null) {
return;
}
String clientKey = channel.attr(CHANNEL_CLIENT_KEY).get();
Channel channel0 = cmdChannels.remove(clientKey);
if (channel != channel0) {
cmdChannels.put(clientKey, channel);
}
List<Integer> ports = channel.attr(CHANNEL_PORT).get();
for (int port : ports) {
Channel proxyChannel = portCmdChannelMapping.remove(port);
if (proxyChannel == null) {
continue;
}
// 在执行断连之前新的连接已经连上来了
if (proxyChannel != channel) {
portCmdChannelMapping.put(port, proxyChannel);
}
}
if (channel.isActive()) {
logger.info("disconnect proxy channel {}", channel);
channel.close();
}
Map<String, Channel> userChannels = getUserChannels(channel);
Iterator<String> ite = userChannels.keySet().iterator();
while (ite.hasNext()) {
Channel userChannel = userChannels.get(ite.next());
if (userChannel.isActive()) {
userChannel.close();
logger.info("disconnect user channel {}", userChannel);
}
}
}
public static Channel getCmdChannel(Integer port) {
return portCmdChannelMapping.get(port);
}
public static Channel getCmdChannel(String clientKey) {
return cmdChannels.get(clientKey);
}
/**
*
*
* @param proxyChannel
* @param userId
* @param userChannel
*/
public static void addUserChannelToCmdChannel(Channel cmdChannel, String userId, Channel userChannel) {
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
userChannel.attr(Constants.USER_ID).set(userId);
userChannel.attr(REQUEST_LAN_INFO).set(lanInfo);
cmdChannel.attr(USER_CHANNELS).get().put(userId, userChannel);
}
/**
*
*
* @param proxyChannel
* @param userId
* @return
*/
public static Channel removeUserChannelFromCmdChannel(Channel cmdChannel, String userId) {
if (cmdChannel.attr(USER_CHANNELS).get() == null) {
return null;
}
synchronized (cmdChannel) {
return cmdChannel.attr(USER_CHANNELS).get().remove(userId);
}
}
/**
*
*
* @param proxyChannel
* @param userId
* @return
*/
public static Channel getUserChannel(Channel cmdChannel, String userId) {
return cmdChannel.attr(USER_CHANNELS).get().get(userId);
}
/**
*
*
* @param userChannel
* @return
*/
public static String getUserChannelUserId(Channel userChannel) {
return userChannel.attr(Constants.USER_ID).get();
}
/**
* IP
*
* @param userChannel
* @return
*/
public static String getUserChannelRequestLanInfo(Channel userChannel) {
return userChannel.attr(REQUEST_LAN_INFO).get();
}
/**
*
*
* @param cmdChannel
* @return
*/
public static Map<String, Channel> getUserChannels(Channel cmdChannel) {
return cmdChannel.attr(USER_CHANNELS).get();
}
}

View File

@ -0,0 +1,177 @@
package org.fengfei.lanproxy.server;
import java.net.BindException;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.common.container.Container;
import org.fengfei.lanproxy.common.container.ContainerHelper;
import org.fengfei.lanproxy.protocol.IdleCheckHandler;
import org.fengfei.lanproxy.protocol.ProxyMessageDecoder;
import org.fengfei.lanproxy.protocol.ProxyMessageEncoder;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import org.fengfei.lanproxy.server.config.ProxyConfig.ConfigChangedListener;
import org.fengfei.lanproxy.server.config.web.WebConfigContainer;
import org.fengfei.lanproxy.server.handlers.ServerChannelHandler;
import org.fengfei.lanproxy.server.handlers.UserChannelHandler;
import org.fengfei.lanproxy.server.metrics.handler.BytesMetricsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
public class ProxyServerContainer implements Container, ConfigChangedListener {
/**
* max packet is 6M.
*/
private static final int MAX_FRAME_LENGTH = 6 * 1024 * 1024;
private static final int LENGTH_FIELD_OFFSET = 0;
private static final int LENGTH_FIELD_LENGTH = 4;
private static final int INITIAL_BYTES_TO_STRIP = 0;
private static final int LENGTH_ADJUSTMENT = 0;
private static Logger logger = LoggerFactory.getLogger(ProxyServerContainer.class);
private NioEventLoopGroup serverWorkerGroup;
private NioEventLoopGroup serverBossGroup;
public ProxyServerContainer() {
serverBossGroup = new NioEventLoopGroup();
serverWorkerGroup = new NioEventLoopGroup();
ProxyConfig.getInstance().addConfigChangedListener(this);
}
@Override
public void start() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
ch.pipeline().addLast(new ServerChannelHandler());
}
});
try {
bootstrap.bind(ProxyConfig.getInstance().getServerBind(), ProxyConfig.getInstance().getServerPort()).get();
logger.info("proxy server start on port " + ProxyConfig.getInstance().getServerPort());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (Config.getInstance().getBooleanValue("server.ssl.enable", false)) {
String host = Config.getInstance().getStringValue("server.ssl.bind", "0.0.0.0");
int port = Config.getInstance().getIntValue("server.ssl.port");
initializeSSLTCPTransport(host, port, new SslContextCreator().initSSLContext());
}
startUserPort();
}
private void initializeSSLTCPTransport(String host, int port, final SSLContext sslContext) {
ServerBootstrap b = new ServerBootstrap();
b.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
try {
pipeline.addLast("ssl", createSslHandler(sslContext, Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false)));
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
ch.pipeline().addLast(new ServerChannelHandler());
} catch (Throwable th) {
logger.error("Severe error during pipeline creation", th);
throw th;
}
}
});
try {
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(host, port);
f.sync();
logger.info("proxy ssl server start on port {}", port);
} catch (InterruptedException ex) {
logger.error("An interruptedException was caught while initializing server", ex);
}
}
private void startUserPort() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new UserChannelHandler());
}
});
List<Integer> ports = ProxyConfig.getInstance().getUserPorts();
for (int port : ports) {
try {
bootstrap.bind(port).get();
logger.info("bind user port " + port);
} catch (Exception ex) {
// BindException表示该端口已经绑定过
if (!(ex.getCause() instanceof BindException)) {
throw new RuntimeException(ex);
}
}
}
}
@Override
public void onChanged() {
startUserPort();
}
@Override
public void stop() {
serverBossGroup.shutdownGracefully();
serverWorkerGroup.shutdownGracefully();
}
private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
if (needsClientAuth) {
sslEngine.setNeedClientAuth(true);
}
return new SslHandler(sslEngine);
}
public static void main(String[] args) {
ContainerHelper.start(Arrays.asList(new Container[] { new ProxyServerContainer(), new WebConfigContainer() }));
}
}

View File

@ -0,0 +1,115 @@
package org.fengfei.lanproxy.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.fengfei.lanproxy.common.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SslContextCreator {
private static Logger logger = LoggerFactory.getLogger(SslContextCreator.class);
public SSLContext initSSLContext() {
logger.info("Checking SSL configuration properties...");
final String jksPath = Config.getInstance().getStringValue("server.ssl.jksPath");
logger.info("Initializing SSL context. KeystorePath = {}.", jksPath);
if (jksPath == null || jksPath.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore path is null or empty. The SSL context won't be initialized.");
return null;
}
// if we have the port also the jks then keyStorePassword and
// keyManagerPassword
// has to be defined
final String keyStorePassword = Config.getInstance().getStringValue("server.ssl.keyStorePassword");
final String keyManagerPassword = Config.getInstance().getStringValue("server.ssl.keyManagerPassword");
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
// key_store_password or key_manager_password are empty
logger.warn("The keystore password is null or empty. The SSL context won't be initialized.");
return null;
}
if (keyManagerPassword == null || keyManagerPassword.isEmpty()) {
// key_manager_password or key_manager_password are empty
logger.warn("The key manager password is null or empty. The SSL context won't be initialized.");
return null;
}
// if client authentification is enabled a trustmanager needs to be
// added to the ServerContext
boolean needsClientAuth = Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false);
try {
logger.info("Loading keystore. KeystorePath = {}.", jksPath);
InputStream jksInputStream = jksDatastore(jksPath);
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
logger.info("Initializing key manager...");
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
TrustManager[] trustManagers = null;
if (needsClientAuth) {
logger.warn(
"Client authentication is enabled. The keystore will be used as a truststore. KeystorePath = {}.",
jksPath);
// use keystore as truststore, as server needs to trust
// certificates signed by the
// server certificates
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
trustManagers = tmf.getTrustManagers();
}
// init sslContext
logger.info("Initializing SSL context...");
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
logger.info("The SSL context has been initialized successfully.");
return serverContext;
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
| KeyManagementException | IOException ex) {
logger.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(),
ex.getMessage());
return null;
}
}
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
if (jksUrl != null) {
logger.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
return getClass().getClassLoader().getResourceAsStream(jksPath);
}
logger.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
File jksFile = new File(jksPath);
if (jksFile.exists()) {
logger.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
return new FileInputStream(jksFile);
}
logger.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath());
return null;
}
}

View File

@ -0,0 +1,412 @@
package org.fengfei.lanproxy.server.config;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fengfei.lanproxy.common.Config;
import org.fengfei.lanproxy.common.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.reflect.TypeToken;
/**
* server config
*
* @author fengfei
*
*/
public class ProxyConfig implements Serializable {
private static final long serialVersionUID = 1L;
/** 配置文件为config.json */
public static final String CONFIG_FILE;
private static Logger logger = LoggerFactory.getLogger(ProxyConfig.class);
static {
// 代理配置信息存放在用户根目录下
String dataPath = System.getProperty("user.home") + "/" + ".lanproxy/";
File file = new File(dataPath);
if (!file.isDirectory()) {
file.mkdir();
}
CONFIG_FILE = dataPath + "/config.json";
}
/** 代理服务器绑定主机host */
private String serverBind;
/** 代理服务器与代理客户端通信端口 */
private Integer serverPort;
/** 配置服务绑定主机host */
private String configServerBind;
/** 配置服务端口 */
private Integer configServerPort;
/** 配置服务管理员用户名 */
private String configAdminUsername;
/** 配置服务管理员密码 */
private String configAdminPassword;
/** 代理客户端,支持多个客户端 */
private List<Client> clients;
/** 更新配置后保证在其他线程即时生效 */
private static ProxyConfig instance = new ProxyConfig();;
/** 代理服务器为各个代理客户端key开启对应的端口列表value */
private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>();
/** 代理服务器上的每个对外端口key对应的代理客户端背后的真实服务器信息value */
private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>();
/** 配置变化监听器 */
private List<ConfigChangedListener> configChangedListeners = new ArrayList<ConfigChangedListener>();
private ProxyConfig() {
// 代理服务器主机和端口配置初始化
this.serverPort = Config.getInstance().getIntValue("server.port");
this.serverBind = Config.getInstance().getStringValue("server.bind", "0.0.0.0");
// 配置服务器主机和端口配置初始化
this.configServerPort = Config.getInstance().getIntValue("config.server.port");
this.configServerBind = Config.getInstance().getStringValue("config.server.bind", "0.0.0.0");
// 配置服务器管理员登录认证信息
this.configAdminUsername = Config.getInstance().getStringValue("config.admin.username");
this.configAdminPassword = Config.getInstance().getStringValue("config.admin.password");
logger.info(
"config init serverBind {}, serverPort {}, configServerBind {}, configServerPort {}, configAdminUsername {}, configAdminPassword {}",
serverBind, serverPort, configServerBind, configServerPort, configAdminUsername, configAdminPassword);
update(null);
}
public Integer getServerPort() {
return this.serverPort;
}
public String getServerBind() {
return serverBind;
}
public void setServerBind(String serverBind) {
this.serverBind = serverBind;
}
public String getConfigServerBind() {
return configServerBind;
}
public void setConfigServerBind(String configServerBind) {
this.configServerBind = configServerBind;
}
public Integer getConfigServerPort() {
return configServerPort;
}
public void setConfigServerPort(Integer configServerPort) {
this.configServerPort = configServerPort;
}
public String getConfigAdminUsername() {
return configAdminUsername;
}
public void setConfigAdminUsername(String configAdminUsername) {
this.configAdminUsername = configAdminUsername;
}
public String getConfigAdminPassword() {
return configAdminPassword;
}
public void setConfigAdminPassword(String configAdminPassword) {
this.configAdminPassword = configAdminPassword;
}
public void setServerPort(Integer serverPort) {
this.serverPort = serverPort;
}
public List<Client> getClients() {
return clients;
}
/**
*
*/
public void update(String proxyMappingConfigJson) {
File file = new File(CONFIG_FILE);
try {
if (proxyMappingConfigJson == null && file.exists()) {
InputStream in = new FileInputStream(file);
byte[] buf = new byte[1024];
ByteArrayOutputStream out = new ByteArrayOutputStream();
int readIndex;
while ((readIndex = in.read(buf)) != -1) {
out.write(buf, 0, readIndex);
}
in.close();
proxyMappingConfigJson = new String(out.toByteArray(), Charset.forName("UTF-8"));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
List<Client> clients = JsonUtil.json2object(proxyMappingConfigJson, new TypeToken<List<Client>>() {
});
if (clients == null) {
clients = new ArrayList<Client>();
}
Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>();
Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>();
// 构造端口映射关系
for (Client client : clients) {
String clientKey = client.getClientKey();
if (clientInetPortMapping.containsKey(clientKey)) {
throw new IllegalArgumentException("密钥同时作为客户端标识,不能重复: " + clientKey);
}
List<ClientProxyMapping> mappings = client.getProxyMappings();
List<Integer> ports = new ArrayList<Integer>();
clientInetPortMapping.put(clientKey, ports);
for (ClientProxyMapping mapping : mappings) {
Integer port = mapping.getInetPort();
ports.add(port);
if (inetPortLanInfoMapping.containsKey(port)) {
throw new IllegalArgumentException("一个公网端口只能映射一个后端信息,不能重复: " + port);
}
inetPortLanInfoMapping.put(port, mapping.getLan());
}
}
// 替换之前的配置关系
this.clientInetPortMapping = clientInetPortMapping;
this.inetPortLanInfoMapping = inetPortLanInfoMapping;
this.clients = clients;
if (proxyMappingConfigJson != null) {
try {
FileOutputStream out = new FileOutputStream(file);
out.write(proxyMappingConfigJson.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
notifyconfigChangedListeners();
}
/**
*
*/
private void notifyconfigChangedListeners() {
List<ConfigChangedListener> changedListeners = new ArrayList<ConfigChangedListener>(configChangedListeners);
for (ConfigChangedListener changedListener : changedListeners) {
changedListener.onChanged();
}
}
/**
*
*
* @param configChangedListener
*/
public void addConfigChangedListener(ConfigChangedListener configChangedListener) {
configChangedListeners.add(configChangedListener);
}
/**
*
*
* @param configChangedListener
*/
public void removeConfigChangedListener(ConfigChangedListener configChangedListener) {
configChangedListeners.remove(configChangedListener);
}
/**
*
*
* @param clientKey
* @return
*/
public List<Integer> getClientInetPorts(String clientKey) {
return clientInetPortMapping.get(clientKey);
}
/**
* clientKey
*
* @return
*/
public Set<String> getClientKeySet() {
return clientInetPortMapping.keySet();
}
/**
*
*
* @param port
* @return
*/
public String getLanInfo(Integer port) {
return inetPortLanInfoMapping.get(port);
}
/**
*
*
* @return
*/
public List<Integer> getUserPorts() {
List<Integer> ports = new ArrayList<Integer>();
Iterator<Integer> ite = inetPortLanInfoMapping.keySet().iterator();
while (ite.hasNext()) {
ports.add(ite.next());
}
return ports;
}
public static ProxyConfig getInstance() {
return instance;
}
/**
*
*
* @author fengfei
*
*/
public static class Client implements Serializable {
private static final long serialVersionUID = 1L;
/** 客户端备注名称 */
private String name;
/** 代理客户端唯一标识key */
private String clientKey;
/** 代理客户端与其后面的真实服务器映射关系 */
private List<ClientProxyMapping> proxyMappings;
private int status;
public String getClientKey() {
return clientKey;
}
public void setClientKey(String clientKey) {
this.clientKey = clientKey;
}
public List<ClientProxyMapping> getProxyMappings() {
return proxyMappings;
}
public void setProxyMappings(List<ClientProxyMapping> proxyMappings) {
this.proxyMappings = proxyMappings;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
/**
*
*
* @author fengfei
*
*/
public static class ClientProxyMapping {
/** 代理服务器端口 */
private Integer inetPort;
/** 需要代理的网络信息(代理客户端能够访问),格式 192.168.1.99:80 (必须带端口) */
private String lan;
/** 备注名称 */
private String name;
public Integer getInetPort() {
return inetPort;
}
public void setInetPort(Integer inetPort) {
this.inetPort = inetPort;
}
public String getLan() {
return lan;
}
public void setLan(String lan) {
this.lan = lan;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
*
*
* @author fengfei
*
*/
public static interface ConfigChangedListener {
void onChanged();
}
}

View File

@ -0,0 +1,94 @@
package org.fengfei.lanproxy.server.config.web;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.fengfei.lanproxy.server.config.web.exception.ContextException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.handler.codec.http.FullHttpRequest;
/**
*
*
* @author fengfei
*
*/
public class ApiRoute {
private static Logger logger = LoggerFactory.getLogger(ApiRoute.class);
/** 接口路由 */
private static Map<String, RequestHandler> routes = new ConcurrentHashMap<String, RequestHandler>();
/** 拦截器,初始化后不会在变化 */
private static List<RequestMiddleware> middlewares = new ArrayList<RequestMiddleware>();
/**
*
*
* @param uri
* @param requestHandler
*/
public static void addRoute(String uri, RequestHandler requestHandler) {
if (routes.containsKey(uri)) {
throw new IllegalArgumentException("Duplicate uri:" + uri);
}
logger.info("add route {}", uri);
routes.put(uri, requestHandler);
}
/**
*
*
* @param requestMiddleware
*/
public static void addMiddleware(RequestMiddleware requestMiddleware) {
if (middlewares.contains(requestMiddleware)) {
throw new IllegalArgumentException("Duplicate RequestMiddleware:" + requestMiddleware);
}
logger.info("add requestMiddleware {}", requestMiddleware);
middlewares.add(requestMiddleware);
}
/**
*
*
* @param request
* @return
*/
public static ResponseInfo run(FullHttpRequest request) {
try {
// 拦截器中如果不能通过以异常的方式进行反馈
for (RequestMiddleware middleware : middlewares) {
middleware.preRequest(request);
}
URI uri = new URI(request.getUri());
RequestHandler handler = routes.get(uri.getPath());
ResponseInfo responseInfo = null;
if (handler != null) {
responseInfo = handler.request(request);
} else {
responseInfo = ResponseInfo.build(ResponseInfo.CODE_API_NOT_FOUND, "api not found");
}
return responseInfo;
} catch (Exception ex) {
if (ex instanceof ContextException) {
return ResponseInfo.build(((ContextException) ex).getCode(), ex.getMessage());
}
logger.error("request error", ex);
}
return null;
}
}

View File

@ -0,0 +1,137 @@
package org.fengfei.lanproxy.server.config.web;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.URI;
import java.nio.charset.Charset;
import org.fengfei.lanproxy.common.JsonUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final String PAGE_FOLDER = System.getProperty("app.home", System.getProperty("user.dir"))
+ "/webpages";
private static final String SERVER_VS = "LPS-0.1";
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
// GET返回页面POST请求接口
if (request.getMethod() != HttpMethod.POST) {
outputPages(ctx, request);
return;
}
ResponseInfo responseInfo = ApiRoute.run(request);
// 错误码规则除100取整为http状态码
outputContent(ctx, request, responseInfo.getCode() / 100, JsonUtil.object2json(responseInfo),
"Application/json;charset=utf-8");
}
private void outputContent(ChannelHandlerContext ctx, FullHttpRequest request, int code, String content,
String mimeType) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(code),
Unpooled.wrappedBuffer(content.getBytes(Charset.forName("UTF-8"))));
response.headers().set(Names.CONTENT_TYPE, mimeType);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(Names.SERVER, SERVER_VS);
ChannelFuture future = ctx.writeAndFlush(response);
if (!HttpHeaders.isKeepAlive(request)) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
/**
*
*
* @param ctx
* @param request
* @throws Exception
*/
private void outputPages(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
HttpResponseStatus status = HttpResponseStatus.OK;
URI uri = new URI(request.getUri());
String uriPath = uri.getPath();
uriPath = uriPath.equals("/") ? "/index.html" : uriPath;
String path = PAGE_FOLDER + uriPath;
File rfile = new File(path);
if (rfile.isDirectory()) {
path = path + "/index.html";
rfile = new File(path);
}
if (!rfile.exists()) {
status = HttpResponseStatus.NOT_FOUND;
outputContent(ctx, request, status.code(), status.toString(), "text/html");
return;
}
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx);
}
String mimeType = MimeType.getMimeType(MimeType.parseSuffix(path));
long length = 0;
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(rfile, "r");
length = raf.length();
} finally {
if (length < 0 && raf != null) {
raf.close();
}
}
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), status);
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, mimeType);
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) {
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, length);
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
response.headers().set(Names.SERVER, SERVER_VS);
ctx.write(response);
if (ctx.pipeline().get(SslHandler.class) == null) {
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
} else {
ctx.write(new ChunkedNioFile(raf.getChannel()));
}
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
private static void send100Continue(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
ctx.writeAndFlush(response);
}
}

View File

@ -0,0 +1,228 @@
package org.fengfei.lanproxy.server.config.web;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MimeType {
final static Pattern pattern = Pattern.compile("\\S*[?]\\S*");
private static Map<String, String> h = new HashMap<String, String>();
static {
h.put("", "application/octet-stream");
h.put("323", "text/h323");
h.put("acx", "application/internet-property-stream");
h.put("ai", "application/postscript");
h.put("aif", "audio/x-aiff");
h.put("aifc", "audio/x-aiff");
h.put("aiff", "audio/x-aiff");
h.put("asf", "video/x-ms-asf");
h.put("asr", "video/x-ms-asf");
h.put("asx", "video/x-ms-asf");
h.put("au", "audio/basic");
h.put("avi", "video/x-msvideo");
h.put("axs", "application/olescript");
h.put("bas", "text/plain");
h.put("bcpio", "application/x-bcpio");
h.put("bin", "application/octet-stream");
h.put("bmp", "image/bmp");
h.put("c", "text/plain");
h.put("cat", "application/vnd.ms-pkiseccat");
h.put("cdf", "application/x-cdf");
h.put("cer", "application/x-x509-ca-cert");
h.put("class", "application/octet-stream");
h.put("clp", "application/x-msclip");
h.put("cmx", "image/x-cmx");
h.put("cod", "image/cis-cod");
h.put("cpio", "application/x-cpio");
h.put("crd", "application/x-mscardfile");
h.put("crl", "application/pkix-crl");
h.put("crt", "application/x-x509-ca-cert");
h.put("csh", "application/x-csh");
h.put("css", "text/css");
h.put("dcr", "application/x-director");
h.put("der", "application/x-x509-ca-cert");
h.put("dir", "application/x-director");
h.put("dll", "application/x-msdownload");
h.put("dms", "application/octet-stream");
h.put("doc", "application/msword");
h.put("dot", "application/msword");
h.put("dvi", "application/x-dvi");
h.put("dxr", "application/x-director");
h.put("eps", "application/postscript");
h.put("etx", "text/x-setext");
h.put("evy", "application/envoy");
h.put("exe", "application/octet-stream");
h.put("fif", "application/fractals");
h.put("flr", "x-world/x-vrml");
h.put("gif", "image/gif");
h.put("gtar", "application/x-gtar");
h.put("gz", "application/x-gzip");
h.put("h", "text/plain");
h.put("hdf", "applicatin/x-hdf");
h.put("hlp", "application/winhlp");
h.put("hqx", "application/mac-binhex40");
h.put("hta", "application/hta");
h.put("htc", "text/x-component");
h.put("htm", "text/html");
h.put("html", "text/html");
h.put("htt", "text/webviewhtml");
h.put("ico", "image/x-icon");
h.put("ief", "image/ief");
h.put("iii", "application/x-iphone");
h.put("ins", "application/x-internet-signup");
h.put("isp", "application/x-internet-signup");
h.put("jfif", "image/pipeg");
h.put("jpe", "image/jpeg");
h.put("jpeg", "image/jpeg");
h.put("jpg", "image/jpeg");
h.put("js", "application/x-javascript");
h.put("latex", "application/x-latex");
h.put("lha", "application/octet-stream");
h.put("lsf", "video/x-la-asf");
h.put("lsx", "video/x-la-asf");
h.put("lzh", "application/octet-stream");
h.put("m13", "application/x-msmediaview");
h.put("m14", "application/x-msmediaview");
h.put("m3u", "audio/x-mpegurl");
h.put("man", "application/x-troff-man");
h.put("mdb", "application/x-msaccess");
h.put("me", "application/x-troff-me");
h.put("mht", "message/rfc822");
h.put("mhtml", "message/rfc822");
h.put("mid", "audio/mid");
h.put("mny", "application/x-msmoney");
h.put("mov", "video/quicktime");
h.put("movie", "video/x-sgi-movie");
h.put("mp2", "video/mpeg");
h.put("mp3", "audio/mpeg");
h.put("mpa", "video/mpeg");
h.put("mpe", "video/mpeg");
h.put("mpeg", "video/mpeg");
h.put("mpg", "video/mpeg");
h.put("mpp", "application/vnd.ms-project");
h.put("mpv2", "video/mpeg");
h.put("ms", "application/x-troff-ms");
h.put("mvb", "application/x-msmediaview");
h.put("nws", "message/rfc822");
h.put("oda", "application/oda");
h.put("p10", "application/pkcs10");
h.put("p12", "application/x-pkcs12");
h.put("p7b", "application/x-pkcs7-certificates");
h.put("p7c", "application/x-pkcs7-mime");
h.put("p7m", "application/x-pkcs7-mime");
h.put("p7r", "application/x-pkcs7-certreqresp");
h.put("p7s", "application/x-pkcs7-signature");
h.put("pbm", "image/x-portable-bitmap");
h.put("pdf", "application/pdf");
h.put("pfx", "application/x-pkcs12");
h.put("pgm", "image/x-portable-graymap");
h.put("pko", "application/ynd.ms-pkipko");
h.put("pma", "application/x-perfmon");
h.put("pmc", "application/x-perfmon");
h.put("pml", "application/x-perfmon");
h.put("pmr", "application/x-perfmon");
h.put("pmw", "application/x-perfmon");
h.put("pnm", "image/x-portable-anymap");
h.put("pot,", "application/vnd.ms-powerpoint");
h.put("ppm", "image/x-portable-pixmap");
h.put("pps", "application/vnd.ms-powerpoint");
h.put("ppt", "application/vnd.ms-powerpoint");
h.put("prf", "application/pics-rules");
h.put("ps", "application/postscript");
h.put("pub", "application/x-mspublisher");
h.put("qt", "video/quicktime");
h.put("ra", "audio/x-pn-realaudio");
h.put("ram", "audio/x-pn-realaudio");
h.put("ras", "image/x-cmu-raster");
h.put("rgb", "image/x-rgb");
h.put("rmi", "audio/mid");
h.put("roff", "application/x-troff");
h.put("rtf", "application/rtf");
h.put("rtx", "text/richtext");
h.put("scd", "application/x-msschedule");
h.put("sct", "text/scriptlet");
h.put("setpay", "application/set-payment-initiation");
h.put("setreg", "application/set-registration-initiation");
h.put("sh", "application/x-sh");
h.put("shar", "application/x-shar");
h.put("sit", "application/x-stuffit");
h.put("snd", "audio/basic");
h.put("spc", "application/x-pkcs7-certificates");
h.put("spl", "application/futuresplash");
h.put("src", "application/x-wais-source");
h.put("sst", "application/vnd.ms-pkicertstore");
h.put("stl", "application/vnd.ms-pkistl");
h.put("stm", "text/html");
h.put("svg", "image/svg+xml");
h.put("sv4cpio", "application/x-sv4cpio");
h.put("sv4crc", "application/x-sv4crc");
h.put("swf", "application/x-shockwave-flash");
h.put("t", "application/x-troff");
h.put("tar", "application/x-tar");
h.put("tcl", "application/x-tcl");
h.put("tex", "application/x-tex");
h.put("texi", "application/x-texinfo");
h.put("texinfo", "application/x-texinfo");
h.put("tgz", "application/x-compressed");
h.put("tif", "image/tiff");
h.put("tiff", "image/tiff");
h.put("tr", "application/x-troff");
h.put("trm", "application/x-msterminal");
h.put("tsv", "text/tab-separated-values");
h.put("txt", "text/plain");
h.put("uls", "text/iuls");
h.put("ustar", "application/x-ustar");
h.put("vcf", "text/x-vcard");
h.put("vrml", "x-world/x-vrml");
h.put("wav", "audio/x-wav");
h.put("wcm", "application/vnd.ms-works");
h.put("wdb", "application/vnd.ms-works");
h.put("wks", "application/vnd.ms-works");
h.put("wmf", "application/x-msmetafile");
h.put("wps", "application/vnd.ms-works");
h.put("wri", "application/x-mswrite");
h.put("wrl", "x-world/x-vrml");
h.put("wrz", "x-world/x-vrml");
h.put("xaf", "x-world/x-vrml");
h.put("xbm", "image/x-xbitmap");
h.put("xla", "application/vnd.ms-excel");
h.put("xlc", "application/vnd.ms-excel");
h.put("xlm", "application/vnd.ms-excel");
h.put("xls", "application/vnd.ms-excel");
h.put("xlt", "application/vnd.ms-excel");
h.put("xlw", "application/vnd.ms-excel");
h.put("xof", "x-world/x-vrml");
h.put("xpm", "image/x-xpixmap");
h.put("xwd", "image/x-xwindowdump");
h.put("z", "application/x-compress");
h.put("zip", "application/zip");
}
public static String getMimeType(String docType) {
String mime = h.get(docType);
if (mime == null) {
mime = "application/octet-stream";
}
return mime;
}
public static String parseSuffix(String url) {
try {
Matcher matcher = pattern.matcher(url);
String[] spUrl = url.toString().split("/");
int len = spUrl.length;
String endUrl = spUrl[len - 1];
if (matcher.find()) {
String[] spEndUrl = endUrl.split("\\?");
endUrl = spEndUrl[0];
}
String[] endUrlArr = endUrl.split("\\.");
return endUrlArr[endUrlArr.length - 1];
} catch (Exception e) {
return "";
}
}
}

View File

@ -0,0 +1,20 @@
package org.fengfei.lanproxy.server.config.web;
import io.netty.handler.codec.http.FullHttpRequest;
/**
*
*
* @author fengfei
*
*/
public interface RequestHandler {
/**
*
*
* @param request
* @return
*/
ResponseInfo request(FullHttpRequest request);
}

View File

@ -0,0 +1,19 @@
package org.fengfei.lanproxy.server.config.web;
import io.netty.handler.codec.http.FullHttpRequest;
/**
*
*
* @author fengfei
*
*/
public interface RequestMiddleware {
/**
*
*
* @param request
*/
void preRequest(FullHttpRequest request);
}

View File

@ -0,0 +1,75 @@
package org.fengfei.lanproxy.server.config.web;
import java.io.Serializable;
public class ResponseInfo implements Serializable {
private static final long serialVersionUID = 1L;
public static final int CODE_OK = 20000;
public static final int CODE_INVILID_PARAMS = 40000;
public static final int CODE_UNAUTHORIZED = 40100;
public static final int CODE_API_NOT_FOUND = 40400;
public static final int CODE_SYSTEM_ERROR = 50000;
private int code;
private String message;
private Object data;
public static ResponseInfo build(int code, String message, Object data) {
return new ResponseInfo(code, message, data);
}
public static ResponseInfo build(int code, String message) {
return new ResponseInfo(code, message);
}
public static ResponseInfo build(Object data) {
return new ResponseInfo(data);
}
private ResponseInfo(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
private ResponseInfo(int code, String message) {
this(code, message, null);
}
private ResponseInfo(Object data) {
this(ResponseInfo.CODE_OK, "success", data);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}

View File

@ -0,0 +1,69 @@
package org.fengfei.lanproxy.server.config.web;
import org.fengfei.lanproxy.common.container.Container;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import org.fengfei.lanproxy.server.config.web.routes.RouteConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WebConfigContainer implements Container {
private static Logger logger = LoggerFactory.getLogger(WebConfigContainer.class);
private NioEventLoopGroup serverWorkerGroup;
private NioEventLoopGroup serverBossGroup;
public WebConfigContainer() {
// 配置管理,并发处理很小,使用单线程处理网络事件
serverBossGroup = new NioEventLoopGroup(1);
serverWorkerGroup = new NioEventLoopGroup(1);
}
@Override
public void start() {
ServerBootstrap httpServerBootstrap = new ServerBootstrap();
httpServerBootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpRequestHandler());
}
});
try {
httpServerBootstrap.bind(ProxyConfig.getInstance().getConfigServerBind(),
ProxyConfig.getInstance().getConfigServerPort()).get();
logger.info("http server start on port " + ProxyConfig.getInstance().getConfigServerPort());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
RouteConfig.init();
}
@Override
public void stop() {
serverBossGroup.shutdownGracefully();
serverWorkerGroup.shutdownGracefully();
}
}

View File

@ -0,0 +1,22 @@
package org.fengfei.lanproxy.server.config.web.exception;
public class ContextException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
public ContextException(int code, String message) {
super(message);
this.code = code;
}
public ContextException(int code) {
super();
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,175 @@
package org.fengfei.lanproxy.server.config.web.routes;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.fengfei.lanproxy.common.JsonUtil;
import org.fengfei.lanproxy.server.ProxyChannelManager;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import org.fengfei.lanproxy.server.config.ProxyConfig.Client;
import org.fengfei.lanproxy.server.config.web.ApiRoute;
import org.fengfei.lanproxy.server.config.web.RequestHandler;
import org.fengfei.lanproxy.server.config.web.RequestMiddleware;
import org.fengfei.lanproxy.server.config.web.ResponseInfo;
import org.fengfei.lanproxy.server.config.web.exception.ContextException;
import org.fengfei.lanproxy.server.metrics.MetricsCollector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.reflect.TypeToken;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
/**
*
*
* @author fengfei
*
*/
public class RouteConfig {
protected static final String AUTH_COOKIE_KEY = "token";
private static Logger logger = LoggerFactory.getLogger(RouteConfig.class);
/** 管理员不能同时在多个地方登录 */
private static String token;
public static void init() {
ApiRoute.addMiddleware(new RequestMiddleware() {
@Override
public void preRequest(FullHttpRequest request) {
String cookieHeader = request.headers().get(HttpHeaders.Names.COOKIE);
boolean authenticated = false;
if (cookieHeader != null) {
String[] cookies = cookieHeader.split(";");
for (String cookie : cookies) {
String[] cookieArr = cookie.split("=");
if (AUTH_COOKIE_KEY.equals(cookieArr[0].trim())) {
if (cookieArr.length == 2 && cookieArr[1].equals(token)) {
authenticated = true;
}
}
}
}
String auth = request.headers().get(HttpHeaders.Names.AUTHORIZATION);
if (!authenticated && auth != null) {
String[] authArr = auth.split(" ");
if (authArr.length == 2 && authArr[0].equals(ProxyConfig.getInstance().getConfigAdminUsername()) && authArr[1].equals(ProxyConfig.getInstance().getConfigAdminPassword())) {
authenticated = true;
}
}
if (!request.getUri().equals("/login") && !authenticated) {
throw new ContextException(ResponseInfo.CODE_UNAUTHORIZED);
}
logger.info("handle request for api {}", request.getUri());
}
});
// 获取配置详细信息
ApiRoute.addRoute("/config/detail", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
List<Client> clients = ProxyConfig.getInstance().getClients();
for (Client client : clients) {
Channel channel = ProxyChannelManager.getCmdChannel(client.getClientKey());
if (channel != null) {
client.setStatus(1);// online
} else {
client.setStatus(0);// offline
}
}
return ResponseInfo.build(ProxyConfig.getInstance().getClients());
}
});
// 更新配置
ApiRoute.addRoute("/config/update", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
byte[] buf = new byte[request.content().readableBytes()];
request.content().readBytes(buf);
String config = new String(buf, Charset.forName("UTF-8"));
List<Client> clients = JsonUtil.json2object(config, new TypeToken<List<Client>>() {
});
if (clients == null) {
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error json config");
}
try {
ProxyConfig.getInstance().update(config);
} catch (Exception ex) {
logger.error("config update error", ex);
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, ex.getMessage());
}
return ResponseInfo.build(ResponseInfo.CODE_OK, "success");
}
});
ApiRoute.addRoute("/login", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
byte[] buf = new byte[request.content().readableBytes()];
request.content().readBytes(buf);
String config = new String(buf);
Map<String, String> loginParams = JsonUtil.json2object(config, new TypeToken<Map<String, String>>() {
});
if (loginParams == null) {
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error login info");
}
String username = loginParams.get("username");
String password = loginParams.get("password");
if (username == null || password == null) {
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password");
}
if (username.equals(ProxyConfig.getInstance().getConfigAdminUsername()) && password.equals(ProxyConfig.getInstance().getConfigAdminPassword())) {
token = UUID.randomUUID().toString().replace("-", "");
return ResponseInfo.build(token);
}
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password");
}
});
ApiRoute.addRoute("/logout", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
token = null;
return ResponseInfo.build(ResponseInfo.CODE_OK, "success");
}
});
ApiRoute.addRoute("/metrics/get", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
return ResponseInfo.build(MetricsCollector.getAllMetrics());
}
});
ApiRoute.addRoute("/metrics/getandreset", new RequestHandler() {
@Override
public ResponseInfo request(FullHttpRequest request) {
return ResponseInfo.build(MetricsCollector.getAndResetAllMetrics());
}
});
}
}

View File

@ -0,0 +1,191 @@
package org.fengfei.lanproxy.server.handlers;
import java.util.List;
import org.fengfei.lanproxy.protocol.Constants;
import org.fengfei.lanproxy.protocol.ProxyMessage;
import org.fengfei.lanproxy.server.ProxyChannelManager;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
/**
*
* @author fengfei
*
*/
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static Logger logger = LoggerFactory.getLogger(ServerChannelHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
logger.debug("ProxyMessage received {}", proxyMessage.getType());
switch (proxyMessage.getType()) {
case ProxyMessage.TYPE_HEARTBEAT:
handleHeartbeatMessage(ctx, proxyMessage);
break;
case ProxyMessage.C_TYPE_AUTH:
handleAuthMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_CONNECT:
handleConnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.TYPE_DISCONNECT:
handleDisconnectMessage(ctx, proxyMessage);
break;
case ProxyMessage.P_TYPE_TRANSFER:
handleTransferMessage(ctx, proxyMessage);
break;
default:
break;
}
}
private void handleTransferMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null) {
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
userChannel.writeAndFlush(buf);
}
}
private void handleDisconnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
if (clientKey == null) {
String userId = proxyMessage.getUri();
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(ctx.channel(), userId);
if (userChannel != null) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
return;
}
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
if (cmdChannel == null) {
logger.warn("ConnectMessage:error cmd channel key {}", ctx.channel().attr(Constants.CLIENT_KEY).get());
return;
}
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, ctx.channel().attr(Constants.USER_ID).get());
if (userChannel != null) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
ctx.channel().attr(Constants.CLIENT_KEY).remove();
ctx.channel().attr(Constants.USER_ID).remove();
}
}
private void handleConnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String uri = proxyMessage.getUri();
if (uri == null) {
ctx.channel().close();
logger.warn("ConnectMessage:null uri");
return;
}
String[] tokens = uri.split("@");
if (tokens.length != 2) {
ctx.channel().close();
logger.warn("ConnectMessage:error uri");
return;
}
Channel cmdChannel = ProxyChannelManager.getCmdChannel(tokens[1]);
if (cmdChannel == null) {
ctx.channel().close();
logger.warn("ConnectMessage:error cmd channel key {}", tokens[1]);
return;
}
Channel userChannel = ProxyChannelManager.getUserChannel(cmdChannel, tokens[0]);
if (userChannel != null) {
ctx.channel().attr(Constants.USER_ID).set(tokens[0]);
ctx.channel().attr(Constants.CLIENT_KEY).set(tokens[1]);
ctx.channel().attr(Constants.NEXT_CHANNEL).set(userChannel);
userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
userChannel.config().setOption(ChannelOption.AUTO_READ, true);
}
}
private void handleHeartbeatMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
ProxyMessage heartbeatMessage = new ProxyMessage();
heartbeatMessage.setSerialNumber(heartbeatMessage.getSerialNumber());
heartbeatMessage.setType(ProxyMessage.TYPE_HEARTBEAT);
logger.debug("response heartbeat message {}", ctx.channel());
ctx.channel().writeAndFlush(heartbeatMessage);
}
private void handleAuthMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String clientKey = proxyMessage.getUri();
List<Integer> ports = ProxyConfig.getInstance().getClientInetPorts(clientKey);
if (ports == null) {
logger.info("error clientKey {}, {}", clientKey, ctx.channel());
ctx.channel().close();
return;
}
Channel channel = ProxyChannelManager.getCmdChannel(clientKey);
if (channel != null) {
logger.warn("exist channel for key {}, {}", clientKey, channel);
ctx.channel().close();
return;
}
logger.info("set port => channel, {}, {}, {}", clientKey, ports, ctx.channel());
ProxyChannelManager.addCmdChannel(ports, clientKey, ctx.channel());
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null) {
userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null && userChannel.isActive()) {
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
String userId = ctx.channel().attr(Constants.USER_ID).get();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
if (cmdChannel != null) {
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
} else {
logger.warn("null cmdChannel, clientKey is {}", clientKey);
}
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
userChannel.close();
} else {
ProxyChannelManager.removeCmdChannel(ctx.channel());
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("exception caught", cause);
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,142 @@
package org.fengfei.lanproxy.server.handlers;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicLong;
import org.fengfei.lanproxy.protocol.Constants;
import org.fengfei.lanproxy.protocol.ProxyMessage;
import org.fengfei.lanproxy.server.ProxyChannelManager;
import org.fengfei.lanproxy.server.config.ProxyConfig;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* channel.
*/
public class UserChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static AtomicLong userIdProducer = new AtomicLong(0);
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.P_TYPE_TRANSFER);
proxyMessage.setUri(userId);
proxyMessage.setData(bytes);
proxyChannel.writeAndFlush(proxyMessage);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
String userId = newUserId();
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
userChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyChannelManager.addUserChannelToCmdChannel(cmdChannel, userId, userChannel);
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_CONNECT);
proxyMessage.setUri(userId);
proxyMessage.setData(lanInfo.getBytes());
cmdChannel.writeAndFlush(proxyMessage);
}
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开,从控制连接中移除
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.CLIENT_KEY).remove();
proxyChannel.attr(Constants.USER_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端,用户连接已经断开
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
proxyMessage.setUri(userId);
proxyChannel.writeAndFlush(proxyMessage);
}
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
}
}
super.channelWritabilityChanged(ctx);
}
/**
* ID
*
* @return
*/
private static String newUserId() {
return String.valueOf(userIdProducer.incrementAndGet());
}
}

View File

@ -0,0 +1,79 @@
package org.fengfei.lanproxy.server.metrics;
import java.io.Serializable;
public class Metrics implements Serializable {
private static final long serialVersionUID = 1L;
private int port;
private long readBytes;
private long wroteBytes;
private long readMsgs;
private long wroteMsgs;
private int channels;
private long timestamp;
public long getReadBytes() {
return readBytes;
}
public void setReadBytes(long readBytes) {
this.readBytes = readBytes;
}
public long getWroteBytes() {
return wroteBytes;
}
public void setWroteBytes(long wroteBytes) {
this.wroteBytes = wroteBytes;
}
public long getReadMsgs() {
return readMsgs;
}
public void setReadMsgs(long readMsgs) {
this.readMsgs = readMsgs;
}
public long getWroteMsgs() {
return wroteMsgs;
}
public void setWroteMsgs(long wroteMsgs) {
this.wroteMsgs = wroteMsgs;
}
public int getChannels() {
return channels;
}
public void setChannels(int channels) {
this.channels = channels;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View File

@ -0,0 +1,121 @@
package org.fengfei.lanproxy.server.metrics;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class MetricsCollector {
private static Map<Integer, MetricsCollector> metricsCollectors = new ConcurrentHashMap<Integer, MetricsCollector>();
private Integer port;
private AtomicLong readBytes = new AtomicLong();
private AtomicLong wroteBytes = new AtomicLong();
private AtomicLong readMsgs = new AtomicLong();
private AtomicLong wroteMsgs = new AtomicLong();
private AtomicInteger channels = new AtomicInteger();
private MetricsCollector() {
}
public static MetricsCollector getCollector(Integer port) {
MetricsCollector collector = metricsCollectors.get(port);
if (collector == null) {
synchronized (metricsCollectors) {
collector = metricsCollectors.get(port);
if (collector == null) {
collector = new MetricsCollector();
collector.setPort(port);
metricsCollectors.put(port, collector);
}
}
}
return collector;
}
public static List<Metrics> getAndResetAllMetrics() {
List<Metrics> allMetrics = new ArrayList<Metrics>();
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
while (ite.hasNext()) {
allMetrics.add(ite.next().getValue().getAndResetMetrics());
}
return allMetrics;
}
public static List<Metrics> getAllMetrics() {
List<Metrics> allMetrics = new ArrayList<Metrics>();
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
while (ite.hasNext()) {
allMetrics.add(ite.next().getValue().getMetrics());
}
return allMetrics;
}
public Metrics getAndResetMetrics() {
Metrics metrics = new Metrics();
metrics.setChannels(channels.get());
metrics.setPort(port);
metrics.setReadBytes(readBytes.getAndSet(0));
metrics.setWroteBytes(wroteBytes.getAndSet(0));
metrics.setTimestamp(System.currentTimeMillis());
metrics.setReadMsgs(readMsgs.getAndSet(0));
metrics.setWroteMsgs(wroteMsgs.getAndSet(0));
return metrics;
}
public Metrics getMetrics() {
Metrics metrics = new Metrics();
metrics.setChannels(channels.get());
metrics.setPort(port);
metrics.setReadBytes(readBytes.get());
metrics.setWroteBytes(wroteBytes.get());
metrics.setTimestamp(System.currentTimeMillis());
metrics.setReadMsgs(readMsgs.get());
metrics.setWroteMsgs(wroteMsgs.get());
return metrics;
}
public void incrementReadBytes(long bytes) {
readBytes.addAndGet(bytes);
}
public void incrementWroteBytes(long bytes) {
wroteBytes.addAndGet(bytes);
}
public void incrementReadMsgs(long msgs) {
readMsgs.addAndGet(msgs);
}
public void incrementWroteMsgs(long msgs) {
wroteMsgs.addAndGet(msgs);
}
public AtomicInteger getChannels() {
return channels;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}

View File

@ -0,0 +1,46 @@
package org.fengfei.lanproxy.server.metrics.handler;
import java.net.InetSocketAddress;
import org.fengfei.lanproxy.server.metrics.MetricsCollector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
public class BytesMetricsHandler extends ChannelDuplexHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
metricsCollector.incrementReadBytes(((ByteBuf) msg).readableBytes());
metricsCollector.incrementReadMsgs(1);
ctx.fireChannelRead(msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
metricsCollector.incrementWroteBytes(((ByteBuf) msg).readableBytes());
metricsCollector.incrementWroteMsgs(1);
super.write(ctx, msg, promise);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector.getCollector(sa.getPort()).getChannels().incrementAndGet();
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector.getCollector(sa.getPort()).getChannels().decrementAndGet();
super.channelInactive(ctx);
}
}

View File

@ -0,0 +1,18 @@
server.bind=0.0.0.0
server.port=4900
# 服务启动接收请求端口4900
# ssl是否开启默认不开启
server.ssl.enable=false
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
# 8090服务端启动访问端口账号密码
config.server.bind=0.0.0.0
config.server.port=8090
config.admin.username=admin
config.admin.password=qq755141

View File

@ -0,0 +1,13 @@
log4j.rootLogger=info,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.R.File=${app.home}/logs/server.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn

View File

@ -0,0 +1,11 @@
@echo off & setlocal enabledelayedexpansion
title lanproxy-server
cd %~dp0
set LIB_JARS=""
cd ..\lib
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
cd ..\bin
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.server.ProxyServerContainer
goto end

View File

@ -0,0 +1,43 @@
#!/bin/bash
cd `dirname $0`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf
LOGS_DIR=$DEPLOY_DIR/logs
APP_MAINCLASS=org.fengfei.lanproxy.server.ProxyServerContainer
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -n "$PIDS" ]; then
echo "ERROR: already started!"
echo "PID: $PIDS"
exit 1
fi
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
CLOG_FILE=$LOGS_DIR/gc.log
LIB_DIR=$DEPLOY_DIR/lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
fi
JAVA_MEM_OPTS=""
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
echo -e "Starting the proxy server ...\c"
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
sleep 1
echo "started"
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"

View File

@ -0,0 +1,35 @@
#!/bin/bash
cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
LOGS_DIR=$DEPLOY_DIR/logs
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
echo "PID: $PID"
if [ -z "$PID" ]; then
echo "ERROR: The proxy server does not started!"
exit 1
fi
echo -e "Stopping the proxy server ...\c"
kill $PID > $STDOUT_FILE 2>&1
COUNT=0
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
COUNT=1
PID_EXIST=`ps -f -p $PID | grep java`
if [ -n "$PID_EXIST" ]; then
COUNT=0
fi
done
echo "stopped"
echo "PID: $PID"

Binary file not shown.

View File

@ -0,0 +1,11 @@
package org.fengfei.lanproxy.server.test;
import org.fengfei.lanproxy.server.ProxyServerContainer;
public class ServerMainTest {
public static void main(String[] args) {
ProxyServerContainer.main(args);
}
}

View File

@ -0,0 +1,15 @@
server.bind=0.0.0.0
server.port=4900
server.ssl.enable=true
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
config.server.bind=0.0.0.0
config.server.port=8099
config.admin.username=admin
config.admin.password=admin

View File

@ -0,0 +1,7 @@
log4j.rootLogger=info,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
#log4j.logger.io.netty=warn

Binary file not shown.

View File

@ -0,0 +1,18 @@
server.bind=0.0.0.0
server.port=4900
# 服务启动接收请求端口4900
# ssl是否开启默认不开启
server.ssl.enable=false
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
# 8090服务端启动访问端口账号密码
config.server.bind=0.0.0.0
config.server.port=8090
config.admin.username=admin
config.admin.password=qq755141

View File

@ -0,0 +1,13 @@
log4j.rootLogger=info,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.R.File=${app.home}/logs/server.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.io.netty=warn

View File

@ -0,0 +1,11 @@
@echo off & setlocal enabledelayedexpansion
title lanproxy-server
cd %~dp0
set LIB_JARS=""
cd ..\lib
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
cd ..\bin
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.server.ProxyServerContainer
goto end

View File

@ -0,0 +1,43 @@
#!/bin/bash
cd `dirname $0`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf
LOGS_DIR=$DEPLOY_DIR/logs
APP_MAINCLASS=org.fengfei.lanproxy.server.ProxyServerContainer
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -n "$PIDS" ]; then
echo "ERROR: already started!"
echo "PID: $PIDS"
exit 1
fi
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
CLOG_FILE=$LOGS_DIR/gc.log
LIB_DIR=$DEPLOY_DIR/lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
fi
JAVA_MEM_OPTS=""
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
echo -e "Starting the proxy server ...\c"
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
sleep 1
echo "started"
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"

View File

@ -0,0 +1,35 @@
#!/bin/bash
cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
LOGS_DIR=$DEPLOY_DIR/logs
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
echo "PID: $PID"
if [ -z "$PID" ]; then
echo "ERROR: The proxy server does not started!"
exit 1
fi
echo -e "Stopping the proxy server ...\c"
kill $PID > $STDOUT_FILE 2>&1
COUNT=0
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
COUNT=1
PID_EXIST=`ps -f -p $PID | grep java`
if [ -n "$PID_EXIST" ]; then
COUNT=0
fi
done
echo "stopped"
echo "PID: $PID"

Binary file not shown.

View File

@ -0,0 +1,5 @@
#Generated by Maven
#Wed Jul 05 11:03:48 CST 2023
groupId=org.fengfei
artifactId=proxy-server
version=0.1

View File

@ -0,0 +1,36 @@
org\fengfei\lanproxy\server\config\web\HttpRequestHandler.class
org\fengfei\lanproxy\server\config\web\RequestMiddleware.class
org\fengfei\lanproxy\server\config\web\ResponseInfo.class
org\fengfei\lanproxy\server\config\web\WebConfigContainer$1.class
org\fengfei\lanproxy\server\config\ProxyConfig$1.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$1.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$3$1.class
org\fengfei\lanproxy\server\config\ProxyConfig$Client.class
org\fengfei\lanproxy\server\ProxyServerContainer.class
org\fengfei\lanproxy\server\SslContextCreator.class
org\fengfei\lanproxy\server\metrics\handler\BytesMetricsHandler.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$3.class
org\fengfei\lanproxy\server\ProxyServerContainer$2.class
org\fengfei\lanproxy\server\config\web\exception\ContextException.class
org\fengfei\lanproxy\server\config\web\ApiRoute.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$5.class
org\fengfei\lanproxy\server\metrics\Metrics.class
org\fengfei\lanproxy\server\config\web\RequestHandler.class
org\fengfei\lanproxy\server\handlers\ServerChannelHandler.class
org\fengfei\lanproxy\server\config\ProxyConfig.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$2.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$4$1.class
org\fengfei\lanproxy\server\config\web\WebConfigContainer.class
org\fengfei\lanproxy\server\ProxyChannelManager.class
org\fengfei\lanproxy\server\ProxyServerContainer$3.class
org\fengfei\lanproxy\server\config\ProxyConfig$ConfigChangedListener.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$7.class
org\fengfei\lanproxy\server\handlers\UserChannelHandler.class
org\fengfei\lanproxy\server\ProxyChannelManager$1.class
org\fengfei\lanproxy\server\ProxyServerContainer$1.class
org\fengfei\lanproxy\server\config\ProxyConfig$ClientProxyMapping.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$4.class
org\fengfei\lanproxy\server\config\web\MimeType.class
org\fengfei\lanproxy\server\metrics\MetricsCollector.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig.class
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$6.class

View File

@ -0,0 +1,18 @@
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\MetricsCollector.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\RequestMiddleware.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\handlers\ServerChannelHandler.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\handler\BytesMetricsHandler.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\ProxyConfig.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\RequestHandler.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\Metrics.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\routes\RouteConfig.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\handlers\UserChannelHandler.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\ApiRoute.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\MimeType.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\ProxyChannelManager.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\exception\ContextException.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\HttpRequestHandler.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\ResponseInfo.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\WebConfigContainer.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\SslContextCreator.java
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\ProxyServerContainer.java

View File

@ -0,0 +1 @@
org\fengfei\lanproxy\server\test\ServerMainTest.class

View File

@ -0,0 +1 @@
E:\yx\lanproxy-0.1\proxy-server\src\test\java\org\fengfei\lanproxy\server\test\ServerMainTest.java

View File

@ -0,0 +1,15 @@
server.bind=0.0.0.0
server.port=4900
server.ssl.enable=true
server.ssl.bind=0.0.0.0
server.ssl.port=4993
server.ssl.jksPath=test.jks
server.ssl.keyStorePassword=123456
server.ssl.keyManagerPassword=123456
server.ssl.needsClientAuth=false
config.server.bind=0.0.0.0
config.server.port=8099
config.admin.username=admin
config.admin.password=admin

View File

@ -0,0 +1,7 @@
log4j.rootLogger=info,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
#log4j.logger.io.netty=warn

Binary file not shown.

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>404 - LanProxy</title>
</head>
<body>404
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More