diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9d8c751 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://tmoonlight.github.io/100lines/111.html'] diff --git a/.gitignore b/.gitignore index 629e18f..e601f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -214,3 +214,4 @@ pip-log.txt #Mr Developer .mr.developer.cfg +/wx/.vscode diff --git a/README.md b/README.md index dc83670..704d834 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,283 @@ - -[![Build Status](https://dev.azure.com/tmoonlight/NSmartProxy/_apis/build/status/tmoonlight.NSmartProxy?branchName=master)](https://dev.azure.com/tmoonlight/NSmartProxy/_build/latest?definitionId=1&branchName=master) + -# NSmartProxy +[![GitHub +release](https://img.shields.io/github/release/tmoonlight/NSmartProxy.svg?logoColor=%21%5BGitHub%20release%5D%28https%3A%2F%2Fimg.shields.io%2Fgithub%2Frelease%2Ftmoonlight%2FNSmartProxy.svg%29)](https://github.com/tmoonlight/NSmartProxy/releases) +[![GitHub](https://img.shields.io/github/license/tmoonlight/NSmartProxy.svg)](https://github.com/tmoonlight/NSmartProxy/blob/master/LICENSE) +[![Build +Status](https://dev.azure.com/tmoonlight/NSmartProxy/_apis/build/status/tmoonlight.NSmartProxy?branchName=master)](https://dev.azure.com/tmoonlight/NSmartProxy/_build/latest?definitionId=1&branchName=master) +
+![Docker Pulls](https://img.shields.io/docker/pulls/tmoonlight/nspclient?label=nspclient%20docker%20pulls) +![Docker Pulls](https://img.shields.io/docker/pulls/tmoonlight/nspserver?label=nspserver%20docker%20pulls)
+中文版 \| +[English](https://github.com/tmoonlight/NSmartProxy/blob/master/README_EN.md) + +NSmartProxy +=========== #### 什么是NSmartProxy?
-NSmartProxy是一款免费的内网穿透工具。 -## 特点 -1. 跨平台,客户端和服务端均可运行在MacOS,Linux,Windows系统上;
-2. 使用方便,配置简单;
-3. 多端映射,一个NSmart Proxy客户端可以同时映射多种服务。 -4. 支持TCP协议栈下的所有协议(已经经过测试的有FTP、Telnet、SMTP、HTTP/HTTPS、POP3、SMB、VNC、RDP。暂不支持UDP协议,开发中。) +NSmartProxy是一款免费的内网穿透工具。
+使用中如果有任何问题和建议,可以[点击这里加入Gitter群组](https://gitter.im/tmoonlight/NSmartProxy)或者[点击这里加入QQ群 +(群号:813170640)](//shang.qq.com/wpa/qunwpa?idkey=139dc3d01be5cc7ac3226c022d832b8ddcc4ec4b64d8755cd4f5c669994970c7)我们一起讨论。 -## 运行原理 -NSmartProxy包含两个服务程序:
-* 服务端(NSmartServer):部署在外网,用来接收来自最终使用者和客户端的反向连接,并将它们进行相互转发。 -* 客户端(NSmartClientRouter):部署在内网,用来转发访问内网各种服务的请求以及响应。 - +目录 +---- + - [特点](#特点) + - [运行原理](#运行原理) + - [客户端安装](#客户端安装) + - [启动准备](#启动准备) + - [使用方法](#使用方法) + - [服务端安装](#服务端安装) + - [启动准备](#启动准备-1) + - [使用方法](#使用方法-1) + - [使用案例](#使用案例) -## 启动准备 -#### Linux/Windows/MacOS -1. 安装[.NET Core Runtime](https://dotnet.microsoft.com/download)
-2. 下载最新版本的[NSmartProxy](https://github.com/tmoonlight/NSmartProxy/releases) +特点 +---- + +1. 跨平台,客户端和服务端均可运行在MacOS,Linux,Windows系统上;
+2. 使用方便,配置简单;
+3. 多端映射,只需安装一个NSmartProxy客户端可映射整个局域网内的多种服务; +4. 支持TCP协议栈下的所有协议(已经经过测试的有FTP、Telnet、SMTP、HTTP/HTTPS、POP3、SMB、VNC、RDP。),以及相当一部分基于UDP的协议(已经经过测试的有DNS查询、mosh服务)。 + +运行原理 +-------- + +NSmartProxy包含两个服务程序:
+* 服务端(NSmartProxy.ServerHost):部署在外网,用来接收来自最终使用者和客户端的反向连接,并将它们进行相互转发。 +* 客户端(NSmartProxyClient):部署在内网,用来转发访问内网各种服务的请求以及响应。 + + +客户端安装 +---------- + +NSmartProxy支持各种基于TCP和UDP服务的端口映射,下面以mstsc,iis,ftp以及mosh服务为例:
+ +### 启动准备 + +NSmartProxy的客户端被打包成三种发布方式:第一种是跨平台包,需要预先安装[.NET +Core环境](https://dotnet.microsoft.com/download)。 +第二种是SCD包(包名带"scd"),无需安装.net环境,用户需要根据自己的平台和架构选择相应的压缩包。第三种是Windows窗体版本(包名带"winform"): +#### Windows +1. 确保客户端的环境在.NET Framework 4.6.1 以上。 +2. 下载最新的窗体版本https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspclient_winform_v1.2_final4.zip + +#### Linux + +- 下载最新版本的NSmartProxyClient,以SCD发布下的linux x64系统为例: + + + + wget https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspserver_scd_linux_v1.2_final4.zip + +#### MacOS + +- 下载最新版本的NSmartProxyClient: + + + + wget https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspclient_scd_osx_v1.2_final4.zip + +#### Docker + +- 如果当前机器上已经有了docker运行环境,则无需安装运行时,直接拉取镜像即可运行,如下脚本在Docker + CE 17.09下测试通过: + + + + sudo docker pull tmoonlight/nspclient + sudo docker run --name mynspclient -dit tmoonlight/nspclient + +### 使用方法 + +1. 打开安装目录下的appsettings.json文件,配置服务地址,映射地址和端口(winform版本也兼容这种配置方式,也可直接进入界面配置):
+ + -## 使用方法 -NSmartProxy支持各种基于TCP服务的端口映射,下面以mstsc,iis,ftp服务为例:
-1. 打开安装目录下的appsettings.json文件,配置服务地址,映射地址和端口:
-``` -{ - "ProviderPort": "19974", //反向连接的端口 - "ProviderConfigPort": "12308", //配置服务的端口 - "ProviderAddress": "2017studio.imwork.net", //配置服务的地址,可以是域名(eg.:domain.com)也可以是ip(eg.:211.5.5.4) - //"ProviderAddress": "192.168.0.106", - - //反向代理客户端,可以配置多个 - "Clients": [ - { - "IP": "127.0.0.1", //反向代理机器的ip - "TargetServicePort": "3389" //反向代理服务的端口 - "ConsumerPort":"3389" //外网访问端口,如被占用,则会从20000开始按顺序分配端口 - }, - { - "IP": "127.0.0.1", - "TargetServicePort": "80" - }, { - "IP": "127.0.0.1", - "TargetServicePort": "21" + "ProviderWebPort": 12309, //服务器端口 + "ProviderAddress": "2017studio.imwork.net", //服务器地址 + + //反向代理客户端列表 + "Clients": [ + {//mstsc远程控制服务 + "IP": "127.0.0.1", //反向代理机器的ip + "TargetServicePort": "3389" //反向代理服务的端口 + "ConsumerPort":"3389" //外网访问端口,如被占用,则会从20000开始按顺序分配端口 + }, + {//网站服务 + "IP": "127.0.0.1", + "TargetServicePort": "80" + }, + {//ftp服务 + "IP": "127.0.0.1", + "TargetServicePort": "21", + "IsCompress" : true, //表示启动传输压缩 + "Description": "这是一个ftp协议。" //描述字段,方便用户在服务端界面识别 + }, + {//mosh服务 + "IP": "192.168.0.168", //安装mosh服务的受控端地址 + "TargetServicePort": "60002", + "ConsumerPort": "30002", + "Protocol": "UDP" //表示是一个UDP协议,如果不加以配置,则以TCP协议来转发 + } + ] } - ] -} + +
2. 运行NSmartProxy客户端
+ +- Linux: + + + + sudo unzip nspclient_scd_linux_v1.2.zip + cd nspclient_scd_linux_v1.2 + chmod +x ./NSmartProxyClient + ./NSmartProxyClient + +- MacOS: + + + + sudo unzip nspclient_osx_linux_v1.2.zip + cd nspclient_scd_osx_v1.2 + chmod +x ./NSmartProxyClient + ./NSmartProxyClient + +- Windows: 解压后运行NSmartProxyWinform.exe即可: + + +
+ +3. 后台运行:
+ 您还可以将NSmartProxy客户端注册为一个后台服务,方法如下: + +- Windows:
+ - 方法一
+
+ + - 方法二
``` -
-2. 运行NSmartProxy
+ rem 注册客户端windows服务 + .\NSmartProxyClient action:install +``` +``` + rem 卸载客户端windows服务 + .\NSmartProxyClient action:uninstall +``` +- MacOS/Linux 暂略 -* Linux: +#### 客户端登陆 +默认情况下,客户端以匿名登陆,这种方式会在NSmartProxyServer端创建一个随机匿名用户(前提是服务端配置了允许匿名登陆),如果想显式使用特定用户登陆,需要在第一次运行时增加-u 用户名 -p 密码参数,程序会在当前目录生成一份凭据(.usercache)方便下次自动登陆。 +例如输入以下指令来生成一个用户名admin,密码admin123的凭据: ``` - sudo unzip client.zip - cd client - sudo dotnet NSmartProxyClient.dll +./NSmartProxyClient -u admin -p admin123 ``` +下次仅需使用: +``` +./NSmartProxyClient +``` +自动采用上次的admin用户登陆,如需恢复匿名登陆,则需要删除当前目录下的.usercache文件。 + +- P.S: + 以上是客户端的配置方法,一般情况下,只要用我的免费服务(2017studio.imwork.net)即可进行内网映射了,如果你还想自己搭建服务端,请接着往下看。 + +服务端安装 +---------- + +这里介绍NSmartProxy服务端的安装方法(linux,windows,MacOS均适用)
+ +### 启动准备 + +- 首先你需要一台具备独立IP的服务器,以下安装过程均在此机器上执行: +#### Linux/Windows/MacOS + +1. NSmartProxy的服务端程序被打包成两种发布方式。第一种是跨平台包,需要预先安装[.NET + Core环境](https://dotnet.microsoft.com/download)。 + 第二种是SCD包(包名带"scd"),无需安装.net环境,用户需要根据自己的平台和架构选择相应的压缩包。
+2. 下载最新版的NSmartProxy服务端: +- Linux: + + wget https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspserver_scd_linux_v1.2_final4.zip + +- Windows:
+下载https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspserver_scd_win_v1.2_final4.zip + +- MacOS: + + + wget https://github.com/tmoonlight/NSmartProxy/releases/download/v1.2_final4/nspserver_scd_osx_v1.2_final4.zip + +#### Docker + +- 无需安装运行时,直接拉取镜像即可运行,运行镜像时需要4组端口:配置端口,反向连接端口,API服务端口,以及使用端口,如下脚本在Docker + CE 17.09下测试通过: + + + + sudo docker pull tmoonlight/nspserver + sudo docker run --name mynspserver -dit -p 7842:7842 -p 7841:7841 -p 12309:12309 -p 20000-20050 tmoonlight/nspserver + +### 使用方法 + +1. 解压缩NSmartProxy服务端的压缩包,以下以SCD发布下的linux系统为例 + + + + unzip nspserver_scd_linux_v1.2_final4.zip + +2. 打开安装目录下的appsettings.json文件,设置反向连接端口和配置服务端口,如果没有特殊需求,默认就好:
+ + + + { + "ReversePort": 7842, //反向连接端口 + "ConfigPort": 7841, //配置服务端口 + "WebAPIPort": 12309 //API服务端口 + } + +3. 运行NSmartProxy
+ +第一步 cd到安装目录
第二步 执行以下命令 +* Linux/MacOS: + + chmod +x ./NSmartProxy.ServerHost + ./NSmartProxy.ServerHost + * Windows: +点击 Win+R 打开运行窗口. 输入 "cmd" 按下Ctrl+Shift+Enter打开管理员身份运行的命令行窗口。cd到安装目录,运行如下指令: + + NSmartProxy.ServerHost - 解压client.zip,运行run.cmd即可 +第三步 登陆http://ip:12309 进入web端,出厂用户密码为admin/admin -* P.S: 以上是客户端的配置方法,一般情况下,只要用我的免费服务(2017studio.imwork.net)即可进行内网映射了,如果您还想自己搭建NSmartProxy服务端,请参考[这里](https://github.com/tmoonlight/NSmartProxy/blob/master/README_SERVER.md)。 + -## 使用案例 +第四步 进入服务端对用户进行各种管理操作 + + + +#### 注册为后台服务
+NSmartProxy客户端和服务端均可以注册为一个后台服务,方法如下: +* Windows + 以管理员身份打开命令行后,cd到程序运行目录,运行以下指令进行服务的注册和卸载: + + + + rem 注册服务端windows服务 + .\NSmartProxy.ServerHost action:install + + rem 卸载服务端windows服务 + .\NSmartProxy.ServerHost action:uninstall + +* MacOS/Linux
+可参考wiki: [How To: 30秒使用Linux搭建一个内网穿透服务端](https://github.com/tmoonlight/NSmartProxy/wiki/How-To:-30%E7%A7%92%E4%BD%BF%E7%94%A8Linux%E6%90%AD%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E6%9C%8D%E5%8A%A1%E7%AB%AF) + +使用案例 +-------- 以上已经讲述了将内网的服务映射到外网的方法,还有更多有趣的用法等着你发掘:
-1.远程开机 -
-2.使用windows远程控制操作办公室电脑 -
-3.告别昂贵的vps,以极低的成本制作一个更强大的服务集群
-4.使用ssh等工具在当事人毫不知情的情况下监控他们的电脑,防止妻子外遇,孩子早恋(比较不推荐)
-...etc -
+1. 远程开机 +2. [使用windows远程控制操作办公室电脑](https://github.com/tmoonlight/NSmartProxy/wiki/How-To:-%E4%BD%BF%E7%94%A8NSmartProxy%E5%AE%9E%E7%8E%B0windows%E4%B8%8A%E7%9A%84%E8%BF%9C%E7%A8%8B%E5%8A%9E%E5%85%AC) +3. 告别昂贵的vps,以极低的成本制作一个更强大的服务集群
diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..583de92 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,90 @@ + + +[![GitHub release](https://img.shields.io/github/release/tmoonlight/NSmartProxy.svg?logoColor=%21%5BGitHub%20release%5D%28https%3A%2F%2Fimg.shields.io%2Fgithub%2Frelease%2Ftmoonlight%2FNSmartProxy.svg%29)](https://github.com/tmoonlight/NSmartProxy/releases) +[![GitHub](https://img.shields.io/github/license/tmoonlight/NSmartProxy.svg)](https://github.com/tmoonlight/NSmartProxy/blob/master/LICENSE) +[![Build Status](https://dev.azure.com/tmoonlight/NSmartProxy/_apis/build/status/tmoonlight.NSmartProxy?branchName=master)](https://dev.azure.com/tmoonlight/NSmartProxy/_build/latest?definitionId=1&branchName=master)
+![Docker Pulls](https://img.shields.io/docker/pulls/tmoonlight/nspclient?label=nspclient%20docker%20pulls) +![Docker Pulls](https://img.shields.io/docker/pulls/tmoonlight/nspserver?label=nspserver%20docker%20pulls)
+[中文版](https://github.com/tmoonlight/NSmartProxy/blob/master/README.md) | English + +# NSmartProxy + +#### What is NSmartProxy? +NSmartProxy is a reverse proxy tool that creates a secure tunnel from a public endpoint to a locally service. + +## Characteristics +1. Cross-platform, client and server can run on MacOS, Linux, Windows systems;
+2. Easy to use and simple to configure;
+3. Multi-end mapping, one NSmartProxy client can map multiple service nodes. + +4. Supports all protocols under the TCP protocol stack (such as FTP, Telnet, SMTP, HTTP/HTTPS, POP3, SMB, VNC, RDP. UDP protocol is not supported at present.) + +## Operating principle +NSmartProxy contains two service programs:
+* Server (NSPServer): Deployed on the external network to receive reverse connections from users and NSPClients and forward them to each other. +* Client (NSPClient): Deployed on the internal network to forward requests and responses to access various services on the intranet. + + +## Preparation +#### Linux/Windows/MacOS +1. Install [.NET Core Runtime](https://dotnet.microsoft.com/download)
+2. Download the latest version of [NSmartProxy](https://github.com/tmoonlight/NSmartProxy/releases) +#### Docker +* You can run the nspserver directly without having to install the runtime: +``` +sudo docker pull tmoonlight/nspclient +sudo docker run --name mynspclient -dit tmoonlight/nspclient +``` + +## Instructions +NSmartProxy supports various port mappings based on TCP services. The following is an example of nspclient configuration which contains mstsc, iis, and ftp services:
+1. Open the appsettings.json file in the installation directory, edit the service address, port,and map-rule as follow:
+``` +{ + "ProviderWebPort": 12309, //Configure the port of the NSPServer service + "ProviderAddress": "2017studio.imwork.net", //Configure the address of the NSPServer service + + //NSPClients, you can configure multiple + "Clients": [ + { + "IP": "127.0.0.1", //Reverse proxy machine ip + "TargetServicePort": "3389" //Port of the reverse proxy service + "ConsumerPort":"3389" //External network access port, if occupied,the nspclient will allocate ports in order from 20000 + }, + { + "IP": "127.0.0.1", + "TargetServicePort": "80" + }, + { + "IP": "127.0.0.1", + "TargetServicePort": "21" + } + ] +} +``` +
+2. Run NSmartProxy
+ +* Linux: +``` + sudo unzip client.zip + cd client + sudo dotnet NSmartProxyClient.dll +``` +* Windows: + + Unzip nspclient*.zip and run NSmartProxyWinform.exe: + + +* P.S: The above is the configuration method of the client. In general, you can use the free service (2017studio.imwork.net) to perform intranet mapping. If you want to build the NSmartProxy server yourself, please click [here](https://github.com/tmoonlight/NSmartProxy/blob/master/README_SERVER.md). + +## Use Cases +We have already described the method of mapping the services of the intranet to the external network, and there are more interesting usages waiting for you to +discover:
+1.Remote boot +
+2.Use windows remote control to operate the office computer +
+3.Say goodbye to expensive vps and make a more powerful service cluster at a very low cost
+...etc +
diff --git a/README_SERVER.md b/README_SERVER.md index 92816de..0eddf19 100644 --- a/README_SERVER.md +++ b/README_SERVER.md @@ -1,39 +1,53 @@ - -# NSmartProxy ServerHost +# NSmartProxy Server -这里介绍NSmartProxy服务端的安装方法(linux,windows,MacOS均适用)
+Here is the installation method of NSmartProxy server (Linux, windows, MacOS are compatible)
-## 启动准备 -* 首先你需要一台具备独立IP的服务器,以下安装过程均在此机器上执行: +## Startup preparation +* First of all, you need a server with a separate IP, the following installation process is performed on this machine: #### Linux/Windows/MacOS -1.安装[.NET Core环境](https://dotnet.microsoft.com/download)
-2.下载最新版的[NSmartProxy](https://github.com/tmoonlight/NSmartProxy/releases) +1. Install [.NET Core Environment](https://dotnet.microsoft.com/download)
+2. Download the latest version of [NSmartProxy](https://github.com/tmoonlight/NSmartProxy/releases) + +#### Docker +* You can run the nspserver directly without having to install the runtime. Four sets of ports are required to run the docker image: configuration port, reverse connection port, API service port and consumer port: +``` +sudo docker pull tmoonlight/nspserver +sudo docker run --name mynspserver -dit -p 7842:7842 -p 7841:7841 -p 12309:12309 -p 20000-20050 tmoonlight/nspserver +``` -## 使用方法 -1. 解压缩NSmartProxy服务端的压缩包。 -2. 打开安装目录下的appsettings.json文件,设置反向连接端口和配置服务端口:
+## Instructions +1. Unzip the package of NSmartProxy server. +2. Open the appsettings.json file in the installation directory, set the reverse connection port and configure the service port:
``` { - "ClientServicePort": 9974, //反向连接端口 - "ConfigServicePort": 12308 //配置服务端口 +  "ReversePort": 7842, //Reverse connection port +  "ConfigPort": 7841, //Configure the service port +  "WebAPIPort": 12309 //API service port } ```
-3. 运行NSmartProxy
+3. Run NSmartProxy Server
-第一步 cd到安装目录
-第二步 执行以下命令 -* Linux/MacOS: + +* Linux/MacOS: +Change directory to the installation directory ,then execute the following command: ``` sudo dotnet NSmartProxy.ServerHost.dll ``` -* Windows: - +* Windows: +Press Windows+R to open the “Run” box. Type “cmd” into the box and then press Ctrl+Shift+Enter to run the command as an administrator. +Change directory to the installation directory ,then execute the following command: ``` -运行安装目录下的run.cmd +dotnet NSmartProxy.ServerHost.dll ``` +In the next step,you can log in to http://youraddress:12309 and enter the web terminal. The default user password is admin/admin. + + + +And enter the server to perform various management operations. + diff --git a/README_SERVER_CN.md b/README_SERVER_CN.md new file mode 100644 index 0000000..7f4c510 --- /dev/null +++ b/README_SERVER_CN.md @@ -0,0 +1,66 @@ + + + +# NSmartProxy ServerHost + +这里介绍NSmartProxy服务端的安装方法(linux,windows,MacOS均适用)
+ +## 启动准备 +* 首先你需要一台具备独立IP的服务器,以下安装过程均在此机器上执行: +#### Linux/Windows/MacOS +1.安装[.NET Core环境](https://dotnet.microsoft.com/download)
+2.下载最新版的[NSmartProxy](https://github.com/tmoonlight/NSmartProxy/releases + +#### Docker +* 无需安装运行时,直接拉取镜像即可运行,运行镜像时需要4组端口:配置端口,反向连接端口,API服务端口,以及使用端口 : +``` +sudo docker pull tmoonlight/nspserver +sudo docker run --name mynspserver -dit -p 7842:7842 -p 7841:7841 -p 12309:12309 -p 20000-20050 tmoonlight/nspserver +``` + +## 使用方法 +1. 解压缩NSmartProxy服务端的压缩包。 +2. 打开安装目录下的appsettings.json文件,设置反向连接端口和配置服务端口:
+``` +{ + "ReversePort": 7842, //反向连接端口 + "ConfigPort": 7841, //配置服务端口 + "WebAPIPort": 12309 //API服务端口 +} +``` +
+3. 运行NSmartProxy
+ +第一步 cd到安装目录
+第二步 执行以下命令 +* Linux/MacOS: +``` +sudo dotnet NSmartProxy.ServerHost.dll +``` +* Windows: +点击 Win+R 打开运行窗口. 输入 “cmd” 按下 Ctrl+Shift+Enter打开管理员身份运行的命令行窗口。 cd到安装目录,运行如下指令: + +``` +dotnet NSmartProxy.ServerHost.dll +``` + +第三步 登陆http://ip:12309 进入web端,出厂用户密码为admin/admin + + + +第四步 进入服务端对用户进行各种管理操作 + + + +* 注册为后台服务
+您还可以将NSmartProxy客户端注册为一个后台服务,方法如下: +以管理员身份打开命令行后,运行以下指令进行服务的注册和卸载: +``` +rem 注册windows服务 +dotnet NSmartProxy.ServerHost.dll action:install +``` + +``` +rem 卸载windows服务 +dotnet NSmartProxy.ServerHost.dll action:uninstall +``` diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 78ac540..6ea3358 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,15 +7,19 @@ trigger: - master pool: - vmImage: 'windows-latest' + vmImage: 'ubuntu-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' + clientImageName: 'nspclient' + serverImageName: 'nspserver' + clientProjectName: 'NSmartProxyClient' + serverProjectName: 'NSmartProxy.ServerHost' steps: -- task: NuGetToolInstaller@0 +# - task: NuGetToolInstaller@0 # - task: NuGetCommand@2 # inputs: @@ -24,7 +28,7 @@ steps: # - task: VSBuild@1 # inputs: # solution: '$(solution)' -# msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' +# msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(System.DefaultWorkingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' # platform: '$(buildPlatform)' # configuration: '$(buildConfiguration)' @@ -32,12 +36,51 @@ steps: # inputs: # platform: '$(buildPlatform)' # configuration: '$(buildConfiguration)' +- bash: | + mkdir ./build + mkdir ./build/$(clientImageName) + mkdir ./build/$(clientImageName)/$(clientProjectName) + mkdir ./build/nspserver + mkdir ./build/nspserver/NSmartProxy.ServerHost + displayName: Create build directory + workingDirectory: '$(System.DefaultWorkingDirectory)/' + - task: DotNetCoreCLI@2 inputs: - command: 'build' - projects: './src/NSmartProxy.ClientRouter/NSmartProxy.ClientRouter.csproj' + command: 'publish' + publishWebProjects: false + projects: './src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj' + arguments: ' --output $(System.DefaultWorkingDirectory)/build/nspserver /p:PublishTrimmed=false' + workingDirectory: '$(System.DefaultWorkingDirectory)/' + zipAfterPublish: false + - task: DotNetCoreCLI@2 inputs: - command: 'build' - projects: './src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj' + command: 'publish' + publishWebProjects: false + projects: './src/NSmartProxyClient/NSmartProxyClient.csproj' + arguments: ' --output $(System.DefaultWorkingDirectory)/build/nspclient /p:PublishTrimmed=false' + workingDirectory: '$(System.DefaultWorkingDirectory)/' + zipAfterPublish: false + + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + docker login -u $(dhName) -p $(dhPwd) + docker build . -t nspclient + docker tag nspclient tmoonlight/nspclient + docker push tmoonlight/nspclient + workingDirectory: '$(System.DefaultWorkingDirectory)/build/nspclient/NSmartProxyClient' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + docker login -u $(dhName) -p $(dhPwd) + docker build . -t nspserver + docker tag nspserver tmoonlight/nspserver + docker push tmoonlight/nspserver + workingDirectory: '$(System.DefaultWorkingDirectory)/build/nspserver/$(serverProjectName)' diff --git a/imgs/servicecn.png b/imgs/servicecn.png new file mode 100644 index 0000000..d669eb8 Binary files /dev/null and b/imgs/servicecn.png differ diff --git a/imgs/serviceen.png b/imgs/serviceen.png new file mode 100644 index 0000000..f3b852a Binary files /dev/null and b/imgs/serviceen.png differ diff --git a/imgs/supervisor.png b/imgs/supervisor.png new file mode 100644 index 0000000..7148b9b Binary files /dev/null and b/imgs/supervisor.png differ diff --git a/plugins/NSmartProxyFTP/FtpClient.cs b/plugins/NSmartProxyFTP/FtpClient.cs new file mode 100644 index 0000000..14d1b92 --- /dev/null +++ b/plugins/NSmartProxyFTP/FtpClient.cs @@ -0,0 +1,517 @@ +//作者:Mcdull +//说明:FTP客户端类,每个客户端封装一个套接字负责接收和返回数据 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Threading; +using System.Net; +using System.IO; +using System.Globalization; + +namespace FtpServer +{ + class FtpClient + { + private Socket currentSocket; + private Thread thread; + private FtpRequest request; + private bool isClosed; + private Encoding encode; + private string rootDir; + + public User user; + public event Action Quit; + public event Action Login; + + public FtpClient(Socket socket, IPAddress pasv_ip, int pasv_port, IPAddress pasv_proxy_ip, int pasv_proxy_port) + { + isClosed = false; + user = new User(); + this.request = new FtpRequest(this, pasv_ip, pasv_port, pasv_proxy_ip, pasv_proxy_port); + this.currentSocket = socket; + encode = Encoding.Default; + } + + public IPAddress IP + { + get { return ((IPEndPoint)currentSocket.RemoteEndPoint).Address; } + } + + public void Start() + { + thread = new Thread(() => + { + SendMessage("220 欢迎使用FTP服务器,你已经连上了服务器..."); + var type = request.Handle(receiveMsg()[0].tokens); + if (type == RequestType.OPTS) + { + if (request.Handle(receiveMsg()[0].tokens) != RequestType.LOGIN_USER) + { + SendMessage("221 命令错误"); + close(); + return; + } + } + else if (type != RequestType.LOGIN_USER) + { + SendMessage("221 命令错误"); + close(); + return; + } + SendMessage("331 请输入用户 " + user.username + " 的登录密码"); + if (request.Handle(receiveMsg()[0].tokens) != RequestType.LOGIN_PASS) + { + SendMessage("221 命令错误"); + close(); + return; + } + var u = FtpServer.Users.SingleOrDefault(p => p.username == user.username && p.password == user.password); + if (u != null) + { + user.isLogin = true; + user.workingDir = rootDir = u.rootDir; + SendMessage("230 用户 " + user.username + " 授权登录."); + onLogin(); + } + else + { + SendMessage("530 用户名或者密码错误。"); + close(); + return; + } + while (user.isLogin && !isClosed) + { + if (currentSocket.Connected) // && currentSocket.Available > 0 为了捕获receiveMsg异常从而关闭连接这里注释掉 + { + var tokens = receiveMsg(); + foreach (var t in tokens) + { + request.Handle(t.tokens); + } + } + Thread.Sleep(500); + } + }); + thread.Start(); + } + + public void Stop() + { + close(false); + if (thread != null) + thread.Abort(); + } + + #region Request处理各种请求方法 + //获取发送来的文件 + public bool ReceiveFile(Socket tempSocket, string filename) + { + string name = getFileName(filename); + FileInfo fi = new FileInfo(name); + if (fi.Exists) + return false; + if (tempSocket != null && tempSocket.Connected) + { + string dir = Path.GetDirectoryName(name); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + byte[] buffer = new byte[1024]; + using (FileStream fs = new FileStream(name, FileMode.CreateNew, FileAccess.Write, FileShare.Write)) + { + fs.Seek(0, SeekOrigin.Begin); + int length = 0; + do + { + length = tempSocket.Receive(buffer); + fs.Write(buffer, 0, length); + } + while (length > 0); + fs.Close(); //这句话可能是多余的 + } + return true; + } + else + { + return false; + } + } + + //为目录或者文件改名 + public int Rename(string from, string to) + { + string name = getFileName(from); + string new_name = getFileName(to); + Console.WriteLine("重命名" + name + "到" + new_name); + FileInfo fi = new FileInfo(name); + if (Directory.Exists(name)) + { + Directory.Move(name, new_name); + return 1; + } + else if (fi.Exists) + { + fi.MoveTo(new_name); + return 2; + } + return 0; + } + + //删除文件或目录 + public int Delete(string dirname) + { + string dir = getFileName(dirname); + Console.WriteLine("删除目录/文件" + dir); + FileInfo fi = new FileInfo(dir); + if (Directory.Exists(dir)) + { + Directory.Delete(dir, true); + return 1; + } + else if (fi.Exists) + { + fi.Delete(); + return 2; + } + return 0; + } + + //跳转到其他目录 + public bool GotoDir(string targetDir) + { + string dir = trimEnd(targetDir); + if (dir == "..") + { + //转到上一级目录 + if (user.workingDir != rootDir) + { + //如果当前目录不是根目录,就转到上一级目录 + try + { + DirectoryInfo di = Directory.GetParent(user.workingDir); + if (di != null) + user.workingDir = di.FullName; + return true; + } + catch (ArgumentNullException) + { + Console.WriteLine("路径为空"); + return false; + } + catch (ArgumentException) + { + Console.WriteLine("当前工作路径是一个空字符串,路径中只能包含空格或者其他任何有效的字符。"); + return false; + } + } + return true; + } + else if (dir == ".") + { + return true; + } + else if (dir[0] == '/') + { + //是否从根目录开始 + dir = dir.TrimStart("/".ToCharArray()); + dir = rootDir + "\\" + dir; + try + { + DirectoryInfo di = new DirectoryInfo(dir); + if (di.Exists) + { + user.workingDir = di.FullName; + return true; + } + else + { + return false; + } + } + catch (NotSupportedException) + { + return false; + } + } + else + { + string workingDirName = new DirectoryInfo(user.workingDir).Name; + var temp = dir.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + if (temp.Length > 0 && workingDirName == temp[0]) + { + int i = dir.IndexOf('/'); + if (i > 0) + { + dir = dir.Substring(i + 1, dir.Length - i - 1); + } + } + //进入目录 + dir = user.workingDir + "\\" + dir; + try + { + DirectoryInfo di = new DirectoryInfo(dir); + if (di.Exists) + { + user.workingDir = di.FullName; + return true; + } + else + { + return false; + } + } + catch (NotSupportedException) + { + return false; + } + } + } + + //创建目录 + public bool CreateDir(string dirName) + { + string dir = getFileName(dirName); + Console.WriteLine("创建目录" + dir); + if (Directory.Exists(dir)) + { + return false; + } + else + { + Directory.CreateDirectory(dir); + return true; + } + } + + //向客户端发送文件 + public byte[] GetFile(string filename) + { + string name = getFileName(filename); + FileInfo fi = new FileInfo(name); + if (fi.Exists) + { + FileStream fs = fi.OpenRead(); + byte[] b = new byte[fs.Length]; + fs.Read(b, 0, b.Length); + fs.Close(); + return b; + } + else + { + return null; + } + } + + //更新传输编码 + public void UpdateEncode(string name, string mode) + { + if (mode.ToUpper() == "ON") + { + switch (name.ToUpper()) + { + case "UTF8": + encode = Encoding.UTF8; + break; + } + } + else + encode = Encoding.Default; + } + + //列表当前目录文件 + public string GetCurrentDirList() + { + string[] dirs = Directory.GetDirectories(user.workingDir); + string[] files = Directory.GetFiles(user.workingDir); + string msg = ""; + DateTimeFormatInfo dateTimeFormat = new CultureInfo("en-US", true).DateTimeFormat; + for (int i = 0; i < files.Length; i++) + { + FileInfo fi = new FileInfo(files[i]); + string name = fi.Name; + //msg += string.Format("{0:yyyy-MM-dd HH:mm:ss} {1} {2}\r\n", fi.LastWriteTimeUtc, fi.Length, name); + msg += string.Format("-rw-r--r-- 1 root root {0} {1} {2} {3:HH:mm} {4}\r\n", fi.Length, dateTimeFormat.GetMonthName(fi.LastWriteTime.Month).Substring(0, 3), + fi.LastWriteTime.Day, fi.LastWriteTime, name); + } + + for (int i = 0; i < dirs.Length; i++) + { + DirectoryInfo di = new DirectoryInfo(dirs[i]); + string name = di.Name; + //msg += string.Format("{0:yyyy-MM-dd HH:mm:ss} {1} {2}\r\n", di.LastWriteTimeUtc, "", name); + msg += string.Format("drw-r--r-- 2 0 0 0 {0} {1} {2:HH:mm} {3}\r\n", dateTimeFormat.GetMonthName(di.LastWriteTime.Month).Substring(0, 3), + di.LastWriteTime.Day, di.LastWriteTime, name); + } + return msg; + } + + //获取当前目录 + public string GetCurrentDir() + { + string dir = user.workingDir; + dir = dir.Remove(0, rootDir.Length); + dir = dir.Replace("\\", "/"); + if (dir == "") + dir = "/"; + else if (dir[0] != '/') + dir = "/" + dir; + return dir; + } + + public void LoginOut() + { + user.isLogin = false; + close(); + } + + /// + /// 当前Socket发送消息 + /// + /// + public void SendMessage(string msg) + { + msg += "\r\n"; + sendMsg(encode.GetBytes(msg.ToCharArray())); + } + + /// + /// 根据前一个PORT指定的Socket发送消息 + /// + /// + public void SendMessageByTempSocket(Socket tempSocket, string msg) + { + if (tempSocket != null && tempSocket.Connected) + { + sendMsg(encode.GetBytes(msg.ToCharArray()), tempSocket); + //sendMsg(Encoding.Default.GetBytes(msg.ToCharArray()), tempSocket); + tempSocket.Close(); + } + } + + public void SendMessageByTempSocket(Socket tempSocket, byte[] msg) + { + if (msg.Length > 0) + { + if (tempSocket != null && tempSocket.Connected) + { + sendMsg(msg, tempSocket); + tempSocket.Close(); + } + } + } + #endregion + + #region 私有方法 + private string getFileName(string name) + { + if (name[0] == '/') + { + name = name.TrimStart("/".ToCharArray()); + return rootDir + "\\" + name; + } + else + return user.workingDir + "\\" + trimEnd(name); + } + + private string trimEnd(string str) + { + string dir = ""; + int pos = str.IndexOf("\r\n"); + if (pos > -1) + { + dir = str.Substring(0, pos); + } + else + { + dir = str; + } + return dir; + } + + private void sendMsg(Byte[] message, Socket socket = null) + { + try + { + if (socket == null) + currentSocket.Send(message, message.Length, 0); + else + socket.Send(message, message.Length, 0); + } + catch + { + + } + } + + private List receiveMsg() + { + List list = new List(); + byte[] buff = new byte[1024]; + try + { + currentSocket.Receive(buff); + string clientCommand = encode.GetString(buff); + clientCommand = clientCommand.Trim("\0".ToCharArray()); + clientCommand = clientCommand.Trim("\r\n".ToCharArray());//"PORT 192,168,0,105,51,49\r\nLIST\r\n"这种情况怎么处理,2条命令同时发来 + var msgs = clientCommand.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + foreach (var msg in msgs) + { + var token = new Token(); + if (msg.Length > 0) + { + var index = msg.IndexOf(" "); + if (index > -1) + { + // token.tokens = msg.Split(new char[] { ' ' }); + token.tokens = new string[2]{ + msg.Substring(0, index), + msg.Substring(index+1, msg.Length- index-1) + }; + + } + else + token.tokens = new string[] { msg }; + } + list.Add(token); + } + } + catch + { + close(); + } + return list; + } + + private void close(bool @event = true) + { + if (!isClosed) + { + isClosed = true; + currentSocket.Close(); + request.Dispose(); + if (@event) + { + var temp = Quit; + if (temp != null) + temp(this); + } + } + } + + private void onLogin() + { + var temp = Login; + if (temp != null) + temp(this); + } + + sealed class Token + { + internal string[] tokens { get; set; } + } + #endregion + } +} diff --git a/plugins/NSmartProxyFTP/FtpRequest.cs b/plugins/NSmartProxyFTP/FtpRequest.cs new file mode 100644 index 0000000..ebeacd9 --- /dev/null +++ b/plugins/NSmartProxyFTP/FtpRequest.cs @@ -0,0 +1,331 @@ +//作者:Mcdull +//说明:FTP命令请求接收处理类 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.Sockets; + +namespace FtpServer +{ + class FtpRequest : IDisposable + { + private RequestType transferType; + private string fileName; + private FtpClient client; + + private TcpListener PASV_listener; //PASV模式启用监听 + private readonly IPAddress PASV_PROXY_IP; + private readonly int PASV_PROXY_PORT; + private readonly IPAddress PASV_IP; + private readonly int PASV_PORT = 5397; + private IPAddress PORT_IP; //PORT模式记录客户端监听地址和端口 + private int PORT_PORT; + + public FtpRequest(FtpClient client, IPAddress pasv_ip, int pasv_port) + { + transferType = RequestType.PORT; + this.client = client; + this.PASV_IP = this.PASV_PROXY_IP = pasv_ip; + this.PASV_PORT = this.PASV_PROXY_PORT = pasv_port; + } + + public FtpRequest(FtpClient client, IPAddress pasv_ip, int pasv_port, IPAddress pasv_proxy_ip, int pasv_proxy_port) : this(client, pasv_ip, pasv_port) + { + this.PASV_PROXY_IP = pasv_proxy_ip; + this.PASV_PROXY_PORT = pasv_proxy_port; + } + + public RequestType Handle(string[] tokens) + { + if (tokens == null || tokens.Length < 1) + { + return RequestType.ERROR; + } + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = tokens[i].Trim("\0".ToCharArray()); + tokens[i] = tokens[i].Trim("\r\n".ToCharArray()); + } + if (tokens[0].ToUpper().IndexOf("XPWD") > -1) + tokens[0] = "XPWD"; + Console.WriteLine("处理命令:" + tokens[0]); + switch (tokens[0].ToUpper()) + { + case "USER": + client.user.username = tokens[1]; + return RequestType.LOGIN_USER; + case "PASS": + client.user.password = tokens[1]; + return RequestType.LOGIN_PASS; + case "SYST": + client.SendMessage("215 " + Environment.OSVersion.ToString()); + return RequestType.SYSTEM; + case "OPTS": + client.SendMessage("200 设置成功"); + var args = tokens[1].Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + client.UpdateEncode(args[0], args[1]); + return RequestType.OPTS; + case "RETR": + fileName = tokens[1]; + byte[] file = client.GetFile(fileName); + if (file == null) + { + client.SendMessage("550 文件不存在"); + } + else if (file.Length == 0) + { + client.SendMessage("550 文件大小为空"); + } + else + { + client.SendMessage("150 开始传输数据"); + client.SendMessageByTempSocket(getTempSocket(), file); + client.SendMessage("226 文件发送完毕"); + } + return RequestType.RETRIEVE; + case "STOR": + fileName = tokens[1]; + client.SendMessage("150 开始传输数据"); + if (client.ReceiveFile(getTempSocket(), fileName)) + client.SendMessage("226 文件接受完毕"); + else + client.SendMessage("550 不能上传文件(文件可能已存在)"); + return RequestType.STORE; + case "RNFR": + fileName = tokens[1]; + client.SendMessage("350"); + return RequestType.RENAME_FROM; + case "RNTO": + int r = client.Rename(fileName, tokens[1]); + fileName = tokens[1]; + switch (r) + { + case 1: + client.SendMessage("250 目录改名成功"); + break; + case 2: + client.SendMessage("250 文件改名成功"); + break; + default: + client.SendMessage("550 目录或者文件不存在"); + break; + } + return RequestType.RENAME_TO; + case "XMKD": + case "MKD": + fileName = tokens[1]; + if (!client.CreateDir(fileName)) + client.SendMessage("221 目录已经存在"); + else + client.SendMessage("250 目录创建成功"); + return RequestType.XMKD; + case "DELE": + fileName = tokens[1]; + switch (client.Delete(fileName)) + { + case 1: + client.SendMessage("250 目录删除成功"); + break; + case 2: + client.SendMessage("250 文件删除成功"); + break; + default: + client.SendMessage("221 目录或者文件不存在"); + break; + } + return RequestType.DELETE; + case "PWD": + case "XPWD": + client.SendMessage("257 当前目录\"" + client.GetCurrentDir() + "\""); + return RequestType.PWD; + case "LIST": + case "NLST": + client.SendMessage("150 显示目录信息"); + client.SendMessageByTempSocket(getTempSocket(), client.GetCurrentDirList()); + client.SendMessage("226 显示完毕"); + return RequestType.LIST; + case "CWD": + fileName = tokens[1]; + if (client.GotoDir(fileName)) + client.SendMessage("257 当前目录\"" + client.GetCurrentDir() + "\""); + else + client.SendMessage("500 目录不存在"); + return RequestType.CWD; + case "CDUP": + if (client.GotoDir("..")) + client.SendMessage("257 当前目录\"" + client.GetCurrentDir() + "\""); + else + client.SendMessage("500 目录不存在"); + return RequestType.CDUP; + case "NOOP": + client.SendMessage("200 NOOP命令成功"); + return RequestType.NOOP; + case "QUIT": + client.SendMessage("221 退出登录"); + client.LoginOut(); + return RequestType.LOGOUT; + case "PORT": + { + transferType = RequestType.PORT; + string[] data = new string[6]; + if (tokens.Length == 2) + data = tokens[1].Split(new char[] { ',' }); + //else if (tokens.Length == 3) + // data = tokens[2].Split(new char[] { ',' }); + else + throw new ArgumentException("PORT命令参数无效"); + PORT_PORT = (Int32.Parse(data[4]) << 8) + Int32.Parse(data[5]); + PORT_IP = IPAddress.Parse(data[0] + "." + data[1] + "." + data[2] + "." + data[3]); + client.SendMessage("200"); + return RequestType.DATA_PORT; + } + case "PASV": + { + transferType = RequestType.PASV; + if (PASV_listener == null) + { + PASV_listener = new TcpListener(PASV_IP, PASV_PORT); + PASV_listener.Start(); + } + string ip = string.Format("{0},{1},{2}", PASV_PROXY_IP.ToString().Replace('.', ','), PASV_PROXY_PORT >> 8, PASV_PROXY_PORT & 0xff); + client.SendMessage(string.Format("227 Entering Passive Mode ({0})", ip)); + return RequestType.PASSIVE; + } + default: + client.SendMessage("221 未知的命令" + tokens[0].ToUpper()); + return RequestType.UNKNOWN_CMD; + } + } + + private Socket getTempSocket() + { + Socket tempSocket = null; + if (transferType == RequestType.PASV) + { + int timeout = 5000; + while (timeout-- > 0) + { + if (PASV_listener.Pending()) + { + tempSocket = PASV_listener.AcceptSocket(); + break; + } + System.Threading.Thread.Sleep(500); + } + } + else + { + tempSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tempSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000); + IPEndPoint hostEndPoint = new IPEndPoint(PORT_IP, PORT_PORT); + try + { + tempSocket.Connect(hostEndPoint); + } + catch (SocketException ex) + { + Console.WriteLine("PORT连接失败:{0}", ex.Message); + return null; + } + } + return tempSocket; + } + + public void Dispose() + { + if (PASV_listener != null) + { + PASV_listener.Stop(); + } + } + } + + enum RequestType + { + /// + /// 发送登录名 + /// + LOGIN_USER, + /// + /// 发送登录密码 + /// + LOGIN_PASS, + /// + /// 请求系统类型 + /// + SYSTEM, + RESTART, + /// + /// 请求获得文件 + /// + RETRIEVE, + /// + /// 存储文件 + /// + STORE, + /// + /// 要重命名的文件 + /// + RENAME_FROM, + /// + /// 重命名为新文件 + /// + RENAME_TO, + ABORT, + /// + /// 删除文件 + /// + DELETE, + /// + /// 创建目录 + /// + XMKD, + /// + /// 显示当前工作目录 + /// + PWD, + /// + /// 请求获得目录信息 + /// + LIST, + /// + /// 等待(NOOP) 此命令不产生什么实际动作,它仅使服务器返回OK。 + /// + NOOP, + /// + /// 表示类型 + /// + REPRESENTATION_TYPE, + /// + /// 退出登录 + /// + LOGOUT, + /// + /// 客户端告知服务器端口 + /// + DATA_PORT, + /// + /// 采用PASV传输方法(理解为客户端不发送PORT命令,即服务器不能确定客户端口) + /// + PASSIVE, + /// + /// 改变工作目录 + /// + CWD, + /// + /// 返回上级目录 + /// + CDUP, + /// + /// 设置传输编码 + /// + OPTS, + CHANGE_DIR_UP, + UNKNOWN_CMD, + PORT, + PASV, + ERROR + } +} diff --git a/plugins/NSmartProxyFTP/FtpServer.cs b/plugins/NSmartProxyFTP/FtpServer.cs new file mode 100644 index 0000000..1826612 --- /dev/null +++ b/plugins/NSmartProxyFTP/FtpServer.cs @@ -0,0 +1,117 @@ +//作者:Mcdull +//说明:FTP服务端类,负责监听端口并建立连接。 +//此处单独封装一个类是为了和UI层分离,使得UI仅需通过捕获事件获取通知而无须关心逻辑 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Threading; +using System.Net; + +namespace FtpServer +{ + class FtpServer + { + private TcpListener listener; + private bool isStart; + private Thread mainThread; + private int maxConnect; + + private IPAddress LocalIP { get; set; } + private int Port { get; set; } + private int PasvPort { get; set; } + public static IEnumerable Users { get; private set; } + + public FtpServer(int port, int pasv_port, int max_connect, IEnumerable users) + { + clients = new List(); + // listener = new TcpListener(ip, port); + listener = new TcpListener(IPAddress.Any, port); + maxConnect = max_connect; + // LocalIP = ip; + LocalIP = IPAddress.Any; + Port = port; + PasvPort = pasv_port; + Users = users; + } + + public List clients; + public event Action clientChange; + + public bool Start(IPAddress pasv_proxy_ip, int pasv_proxy_port) + { + if (!isStart) + { + mainThread = new Thread(p => + { + while (isStart) + { + if (clients.Count < maxConnect) + { + try + { + listener.Start(); + Socket socket = listener.AcceptSocket(); + FtpClient client = new FtpClient(socket, LocalIP, PasvPort, pasv_proxy_ip, pasv_proxy_port); + client.Quit += client_Quit; + client.Login += client_Login; + clients.Add(client); + onClientChange(client); + client.Start(); + } + catch + { + isStart = false; + return; + } + } + else + { + listener.Stop(); + } + Thread.Sleep(2000); + } + }); + mainThread.IsBackground = true; //设置为后台线程,进程关闭后线程强制关闭(前台线程则会导致线程结束后进程才能结束) + mainThread.Start(); + isStart = true; + return true; + } + return false; + } + + public bool Stop() + { + if (isStart) + { + isStart = false; + listener.Stop(); + mainThread.Abort(); + foreach (var c in clients) + c.Stop(); + clients.Clear(); + return true; + } + return false; + } + + void client_Login(FtpClient client) + { + onClientChange(client); + } + + void client_Quit(FtpClient client) + { + clients.Remove(client); + onClientChange(client); + } + + private void onClientChange(FtpClient client) + { + var temp = clientChange; + if (temp != null) + temp(client); + } + } +} diff --git a/plugins/NSmartProxyFTP/FtpUser.cs b/plugins/NSmartProxyFTP/FtpUser.cs new file mode 100644 index 0000000..97ce37c --- /dev/null +++ b/plugins/NSmartProxyFTP/FtpUser.cs @@ -0,0 +1,34 @@ +//作者:Mcdull +//说明:FTP账号类 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Configuration; + +namespace FtpServer +{ + sealed class User + { + public bool isLogin { get; set; } + public string username { get; set; } + public string password { get; set; } + public string workingDir { get; set; } + } + + sealed class UserElement + { + public string username + { + get; set; + } + public string password + { + get; set; + } + public string rootDir + { + get; set; + } + } +} diff --git a/plugins/NSmartProxyFTP/NSmartProxyFTP.csproj b/plugins/NSmartProxyFTP/NSmartProxyFTP.csproj new file mode 100644 index 0000000..e2d76d8 --- /dev/null +++ b/plugins/NSmartProxyFTP/NSmartProxyFTP.csproj @@ -0,0 +1,26 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/plugins/NSmartProxyFTP/Program.cs b/plugins/NSmartProxyFTP/Program.cs new file mode 100644 index 0000000..7fed17a --- /dev/null +++ b/plugins/NSmartProxyFTP/Program.cs @@ -0,0 +1,157 @@ +using log4net; +using log4net.Config; +using Microsoft.Extensions.Configuration; +using NSmartProxy.Client; +using NSmartProxy.Data; +using NSmartProxy.Data.Models; +using NSmartProxy.Interfaces; +using NSmartProxy.Shared; +using System; +using System.IO; +using System.Threading.Tasks; +using FtpServer; +using System.Net; +using System.Text.RegularExpressions; +using System.Collections.Generic; + +namespace NSmartProxyFTP +{ + class Program + { + #region logger + public class Log4netLogger : INSmartLogger + { + public void Debug(object message) + { + //Logger.Debug(message); + Logger.Debug(message); + } + + public void Error(object message, Exception ex) + { + //Logger.Debug(message); + Logger.Error(message, ex); + } + + public void Info(object message) + { + Logger.Info(message); + } + } + #endregion + + public static ILog Logger; + public static IConfigurationRoot Configuration { get; set; } + private static LoginInfo _currentLoginInfo; + static void Main(string[] args) + { + //log + var loggerRepository = LogManager.CreateRepository("NSmartClientRouterRepository"); + XmlConfigurator.Configure(loggerRepository, new FileInfo("log4net.config")); + Logger = LogManager.GetLogger(loggerRepository.Name, "NSmartProxyFTP"); + if (!loggerRepository.Configured) throw new Exception("log config failed."); + Console.ForegroundColor = ConsoleColor.Yellow; + + //用户登录 + if (args.Length == 4) + { + _currentLoginInfo = new LoginInfo(); + _currentLoginInfo.UserName = args[1]; + _currentLoginInfo.UserPwd = args[3]; + } + + Logger.Info($"*** {NSPVersion.NSmartProxyServerName} ***"); + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + + Configuration = builder.Build(); + + //start clientrouter. + try + { + StartClient().Wait(); + } + catch (Exception e) + { + Logger.Error(e.Message); + } + Console.Read(); + Logger.Info("Client terminated,press any key to continue."); + + } + + private static void StartFTP(ClientModel clientModel) + { + var ip = GetIP(Configuration.GetSection("ProviderAddress").Value); + Console.WriteLine("外网IP:" + ip.ToString()); + var users = new List(); + foreach (var u in Configuration.GetSection("FtpUsers").GetChildren()) + { + users.Add(new UserElement() { username = u["username"], password = u["password"], rootDir = u["rootDir"] }); + } + var server = new FtpServer.FtpServer(int.Parse(Configuration.GetSection("FtpPort").Value), int.Parse(Configuration.GetSection("PasvPort").Value), int.Parse(Configuration.GetSection("FtpMaxConnect").Value), users); + server.Start(ip, clientModel.AppList[1].Port); + } + + private static IPAddress GetIP(string server) + { + Regex rx = new Regex(@"((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))"); + if (!rx.IsMatch(server)) + { + IPAddress[] ips = Dns.GetHostAddresses(server); + return ips[0]; + } + return IPAddress.Parse(server); + } + + private static async Task StartClient() + { + + Router clientRouter = new Router(new Log4netLogger()); + //read config from config file. + SetConfig(clientRouter); + if (_currentLoginInfo != null) + { + clientRouter.SetLoginInfo(_currentLoginInfo); + } + + Task tsk = clientRouter.Start(true, StartFTP); + try + { + await tsk; + } + catch (Exception e) + { + Logger.Error(e); + throw; + } + + } + + private static void SetConfig(Router clientRouter) + { + + NSPClientConfig config = new NSPClientConfig(); + config.ProviderAddress = Configuration.GetSection("ProviderAddress").Value; + config.ProviderWebPort = int.Parse(Configuration.GetSection("ProviderWebPort").Value); + var configClients = Configuration.GetSection("Clients").GetChildren(); + foreach (var cli in configClients) + { + int confConsumerPort = 0; + if (cli["ConsumerPort"] != null) confConsumerPort = int.Parse(cli["ConsumerPort"]); + config.Clients.Add(new ClientApp + { + IP = cli["IP"], + TargetServicePort = int.Parse(cli["TargetServicePort"]), + ConsumerPort = confConsumerPort, + Host = cli["Host"], + Protocol = Enum.Parse((cli["Protocol"] ?? "TCP").ToUpper()), + Description = cli["Description"] + }); + } + clientRouter.SetConfiguration(config); + } + } +} diff --git a/plugins/NSmartProxyFTP/appsettings.json b/plugins/NSmartProxyFTP/appsettings.json new file mode 100644 index 0000000..3e861f5 --- /dev/null +++ b/plugins/NSmartProxyFTP/appsettings.json @@ -0,0 +1,39 @@ +{ + "ProviderWebPort": 12309, + "ProviderAddress": "2017studio.imwork.net", + "FtpPort": 5555, + "PasvPort": 5379, + "FtpMaxConnect": 2, //FTP最大连接数 + //FTP用户,可以配置多个 + "FtpUsers": [ + { + "username": "admin", + "password": "654123", + "rootDir": "d:\\" + } + ], + + //反向代理客户端,可以配置多个 + "Clients": [ + + //{ + // "IP": "127.0.0.1", + // "TargetServicePort": "82", + // "Host": "haha.tmoonlight.top", + // "Protocol": "HTTP", + // "ConsumerPort": "443", + // "Description": "测试备注" + //}, + + { + "IP": "127.0.0.1", + "TargetServicePort": "5555", + "ConsumerPort": "20006" + }, + { + "IP": "127.0.0.1", + "TargetServicePort": "5379", + "ConsumerPort": "20007" + } + ] +} \ No newline at end of file diff --git a/plugins/NSmartProxyFTP/log4net.config b/plugins/NSmartProxyFTP/log4net.config new file mode 100644 index 0000000..7ea9795 --- /dev/null +++ b/plugins/NSmartProxyFTP/log4net.config @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sh.exe.stackdump b/sh.exe.stackdump new file mode 100644 index 0000000..426b8b7 --- /dev/null +++ b/sh.exe.stackdump @@ -0,0 +1,11 @@ +Stack trace: +Frame Function Args +00000010002 0018006021E (00180241C10, 001802340B9, 00000010002, 000FFFFBA20) +00000010002 00180048859 (00000000002, 00180329138, 00000000002, 00180329138) +00000010002 00180048892 (00000000002, 00180329448, 00000010002, 00000000008) +00000010002 001800598BC (000FFFFCC72, 000FFFFCC55, 00000000000, 001802343AF) +000FFFFCCD0 00180059960 (645C655C725C635C, 695C745C6E5C655C, 6D5C2D5C6C5C615C, 675C615C6E5C615C) +000FFFFCCD0 00180048FE1 (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 00180047963 (00000000000, 00000000000, 00000000000, 00000000000) +000FFFFFFF0 00180047A14 (00000000000, 00000000000, 00000000000, 00000000000) +End of stack trace diff --git a/src/NSmartProxy.ClientRouter/Authorize/UserCacheManager.cs b/src/NSmartProxy.ClientRouter/Authorize/UserCacheManager.cs new file mode 100644 index 0000000..0479c10 --- /dev/null +++ b/src/NSmartProxy.ClientRouter/Authorize/UserCacheManager.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using NSmartProxy.Data.Models; + +namespace NSmartProxy.Client.Authorize +{ + public class UserCacheManager + { + //private Router router; + //private ClientUserCache clientUserCache; + //private string cachePath; + + //public ClientUserCacheItem GetCurrentUserCache() + //{ + // return clientUserCache.TryGetValue(router.ClientConfig.ProviderAddress + ":" + + // router.ClientConfig.ProviderWebPort, out var userCache) ? userCache : null; + //} + + //private UserCacheManager() + //{ + //} + + /// + /// 通过地址获取用户信息 + /// + /// + /// + /// + public static ClientUserCacheItem GetUserCacheFromEndpoint(string endpoint, string cachePath) + { + ClientUserCache userCache = GetClientUserCache(cachePath); + if (userCache.ContainsKey(endpoint)) + { + return userCache[endpoint]; + } + else + { + return null; + } + + } + + ///// + ///// 初始化 + ///// + ///// + ///// + ///// + //public static UserCacheManager Init(Router pRouter, string cachePath) + //{ + // ClientUserCache userCache = GetClientUserCache(cachePath); + // var userCacheManager = new UserCacheManager + // { + // router = pRouter, + // clientUserCache = userCache + // }; + // return userCacheManager; + //} + + /// + /// 获取整个缓存集合 + /// + /// + /// + public static ClientUserCache GetClientUserCache(string cachePath) + { + ClientUserCache userCache; + if (!File.Exists(cachePath)) + { + File.Create(cachePath).Close(); + } + + try + { + userCache = File.ReadAllText(cachePath).ToObject(); + } + catch //(Exception e) + { + // Console.WriteLine(e); + userCache = null; + } + + if (userCache == null) + { + userCache = new ClientUserCache(); + } + + return userCache; + } + + /// + /// 保存文件 + /// + /// + /// + public static void SaveChanges(string cachePath, ClientUserCache clientUserCache) + { + File.WriteAllText(cachePath, clientUserCache.ToJsonString()); + } + + /// + /// 更新特定的用户 + /// + /// + /// + /// + /// + public static void UpdateUser(string token, string userName, string serverEndPoint, string cachePath) + { + var clientUserCache = GetClientUserCache(cachePath); + if (!clientUserCache.ContainsKey(serverEndPoint)) + { + clientUserCache[serverEndPoint] = new ClientUserCacheItem(); + } + + var item = clientUserCache[serverEndPoint]; + item.Token = token; + item.UserName = userName; + SaveChanges(cachePath, clientUserCache); + } + } +} \ No newline at end of file diff --git a/src/NSmartProxy.ClientRouter/ClientAppWorker.cs b/src/NSmartProxy.ClientRouter/ClientAppWorker.cs index 6de60f3..1d36245 100644 --- a/src/NSmartProxy.ClientRouter/ClientAppWorker.cs +++ b/src/NSmartProxy.ClientRouter/ClientAppWorker.cs @@ -10,7 +10,7 @@ public class ClientAppWorker //private bool isWorking = false; //public List TcpClientGroup = new List(); - public TcpClient Client; + public TcpClient Client;//TODO 还是需要把这里改成复数 public int AppId; //1~255 public int Port; //0~65535 diff --git a/src/NSmartProxy.ClientRouter/Dispatchers/NSPDispatcher.cs b/src/NSmartProxy.ClientRouter/Dispatchers/NSPDispatcher.cs index f4f5cf3..3dfab7b 100644 --- a/src/NSmartProxy.ClientRouter/Dispatchers/NSPDispatcher.cs +++ b/src/NSmartProxy.ClientRouter/Dispatchers/NSPDispatcher.cs @@ -1,27 +1,51 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Text; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NSmartProxy.Data; -using NSmartProxy.Data.DTO; +using NSmartProxy.Data.DTOs; namespace NSmartProxy.ClientRouter.Dispatchers { public class NSPDispatcher { - private string BaseUrl = "localhost"; + private string BaseUrl; + //TODO httpclient的一种解决方案:定时对象 + private static HttpClient _client; + private static Timer _timer = new Timer(obj => + { + _client?.Dispose(); + _client = null; + }); + + //_client.Dispose();_client = null + public NSPDispatcher(string baseUrl) { BaseUrl = baseUrl; } + public static HttpClient Client + { + get + { + if (_client == null) + { + //_timer = new + _client = new HttpClient(); + } + return _client; + } + } + public async Task> LoginFromClient(string username, string userpwd) { string url = $"http://{BaseUrl}/LoginFromClient"; - HttpClient client = new HttpClient(); - var httpmsg = await client.GetAsync($"{url}?username={username}&userpwd={userpwd}").ConfigureAwait(false); + var httpmsg = await Client.GetAsync($"{url}?username={username}&userpwd={userpwd}").ConfigureAwait(false); var httpstr = await httpmsg.Content.ReadAsStringAsync().ConfigureAwait(false); return JsonConvert.DeserializeObject>(httpstr); } @@ -29,14 +53,46 @@ public async Task> LoginFromClient(string user public async Task> Login(string userid, string userpwd) { string url = $"http://{BaseUrl}/LoginFromClientById"; - HttpClient client = new HttpClient(); - var httpmsg = await client.GetAsync($"{url}?username={userid}&userpwd={userpwd}").ConfigureAwait(false); + var httpmsg = await Client.GetAsync($"{url}?username={userid}&userpwd={userpwd}").ConfigureAwait(false); var httpstr = await httpmsg.Content.ReadAsStringAsync().ConfigureAwait(false); return JsonConvert.DeserializeObject>(httpstr); } //TODO 增加一个校验用户token是否合法的方法 //public + //GetServerPorts + public async Task> GetServerPorts() + { + string url = $"http://{BaseUrl}/GetServerPorts"; + var httpmsg = await Client.GetAsync(url).ConfigureAwait(false); + var httpstr = await httpmsg.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(httpstr); + } + + /// + /// 当允许服务端端修改客户端时,从服务端获取配置 + /// + /// + public async Task> GetServerClientConfig(string token) + { + string url = $"http://{BaseUrl}/GetServerClientConfig"; + + CookieContainer cookieContainer = new CookieContainer(); + Cookie cookie = new Cookie("NSPTK", token); + cookie.Domain = BaseUrl.Substring(0, BaseUrl.IndexOf(':')); + cookieContainer.Add(cookie); // 加入Cookie + HttpClientHandler httpClientHandler = new HttpClientHandler() + { + CookieContainer = cookieContainer, + AllowAutoRedirect = true, + UseCookies = true + }; + + HttpClient cookieClient = new HttpClient(httpClientHandler); + var httpmsg = await cookieClient.GetAsync($"{url}?userid=").ConfigureAwait(false); + var httpstr = await httpmsg.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject>(httpstr); + } } } diff --git a/src/NSmartProxy.ClientRouter/NSmartProxy.ClientRouter.csproj b/src/NSmartProxy.ClientRouter/NSmartProxy.ClientRouter.csproj index 69ad6b9..58102fb 100644 --- a/src/NSmartProxy.ClientRouter/NSmartProxy.ClientRouter.csproj +++ b/src/NSmartProxy.ClientRouter/NSmartProxy.ClientRouter.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net6.0;netstandard2.0 diff --git a/src/NSmartProxy.ClientRouter/Router.cs b/src/NSmartProxy.ClientRouter/Router.cs index 08338cd..534cb87 100644 --- a/src/NSmartProxy.ClientRouter/Router.cs +++ b/src/NSmartProxy.ClientRouter/Router.cs @@ -10,8 +10,11 @@ using System.Threading.Tasks; using NSmartProxy.Shared; using System.IO; +using System.Reflection; +using NSmartProxy.Client.Authorize; using NSmartProxy.ClientRouter.Dispatchers; using NSmartProxy.Data.Models; +using NSmartProxy.Infrastructure; namespace NSmartProxy.Client { @@ -42,12 +45,17 @@ public enum ClientStatus public class Router { - public const string NSMART_CLIENT_CACHE_PATH = "./cli_cache_v2.cache"; - CancellationTokenSource ONE_LIVE_TOKEN_SRC; - CancellationTokenSource CANCEL_TOKEN_SRC; - CancellationTokenSource TRANSFERING_TOKEN_SRC; - CancellationTokenSource HEARTBEAT_TOKEN_SRC; - TaskCompletionSource _waiter; + private static string nspClientCachePath = null; + + public static string NSMART_CLIENT_CACHE_FILE = "cli_cache_v3.cache"; + + private CancellationTokenSource ONE_LIVE_TOKEN_SRC; + private CancellationTokenSource CANCEL_TOKEN_SRC; + private CancellationTokenSource TRANSFERING_TOKEN_SRC; + private CancellationTokenSource HEARTBEAT_TOKEN_SRC; + private TaskCompletionSource _waiter; + private NSPDispatcher ClientDispatcher; + //private UserCacheManager userCacheManager; public ServerConnectionManager ConnectionManager; public bool IsStarted = false; @@ -61,6 +69,25 @@ public class Router internal static INSmartLogger Logger = new NullLogger(); //inject internal static Guid TimeStamp; //时间戳,用来标识对象是否已经发生变化 + public static string NspClientCachePath + { + get + { + if (nspClientCachePath == null) + { + //代表nsmartproxy.clientrouter.dll所在的路径 + string assemblyFilePath = Assembly.GetExecutingAssembly().Location; + string assemblyDirPath = Path.GetDirectoryName(assemblyFilePath); + NspClientCachePath = assemblyDirPath + "\\" + NSMART_CLIENT_CACHE_FILE; + //NspClientCachePath = System.Environment.CurrentDirectory + "\\" + NSMART_CLIENT_CACHE_FILE; + + } + + return nspClientCachePath; + } + set => nspClientCachePath = value; + } + public Router() { ONE_LIVE_TOKEN_SRC = new CancellationTokenSource(); @@ -71,9 +98,15 @@ public Router(INSmartLogger logger) : this() Logger = logger; } - public Router SetConfiguration(NSPClientConfig config) + //public Router SetConfiguration(string configstr) + //{ + // SetConfiguration(configstr.ToObject()); + // return this; + //} + public Router SetConfiguration(NSPClientConfig config)//start之前一定要执行该方法,否则出错 { ClientConfig = config; + ClientDispatcher = new NSPDispatcher($"{ClientConfig.ProviderAddress}:{ClientConfig.ProviderWebPort}"); return this; } @@ -89,7 +122,7 @@ public Router SetLoginInfo(LoginInfo loginInfo) /// AlwaysReconnect:始终重试,开启此选项,无论何时,一旦程序在连接不上时都会进行重试,否则只在连接成功后的异常中断时才重试。 /// /// - public async Task Start(bool AlwaysReconnect = false) + public async Task Start(bool AlwaysReconnect = false, Action complete = null) { if (AlwaysReconnect) IsStarted = true; var oneLiveToken = ONE_LIVE_TOKEN_SRC.Token; @@ -104,29 +137,34 @@ public async Task Start(bool AlwaysReconnect = false) _waiter = new TaskCompletionSource(); Router.TimeStamp = Guid.NewGuid(); - var appIdIpPortConfig = ClientConfig.Clients; + int clientId = 0; + + + //0.5 处理登录/重登录/匿名登录逻辑 try { - //登录 - if (CurrentLoginInfo != null) + var clientUserCacheItem = UserCacheManager.GetUserCacheFromEndpoint(GetProviderEndPoint(), NspClientCachePath); + //显式使用用户名密码登录 + if (CurrentLoginInfo != null && CurrentLoginInfo.UserName != string.Empty) { var loginResult = await Login(); arrangedToken = loginResult.Item1; clientId = loginResult.Item2; } - else if (File.Exists(NSMART_CLIENT_CACHE_PATH)) - { //登录缓存 - - arrangedToken = File.ReadAllText(NSMART_CLIENT_CACHE_PATH); - //TODO 这个token的合法性无法保证,如果服务端删除了用户,而这里缓存还存在,会导致无法登录 - //TODO ***** 这是个trick:防止匿名用户被服务端踢了之后无限申请新账号 + else if (clientUserCacheItem != null) + { + //登录缓存 + arrangedToken = clientUserCacheItem.Token; + //这个token的合法性无法保证,如果服务端删除了用户,而这里缓存还存在,会导致无法登录 + //服务端校验token失效之后会主动关闭连接 CurrentLoginInfo = null; } else { + //首次登录 //匿名登录,未提供登录信息时,使用空用户名密码自动注册并尝试匿名登录 Router.Logger.Debug("未提供登录信息,尝试匿名登录"); CurrentLoginInfo = new LoginInfo() { UserName = "", UserPwd = "" }; @@ -134,15 +172,77 @@ public async Task Start(bool AlwaysReconnect = false) arrangedToken = loginResult.Item1; clientId = loginResult.Item2; //保存缓存到磁盘 - File.WriteAllText(NSMART_CLIENT_CACHE_PATH, arrangedToken); + + //File.WriteAllText(NspClientCachePath, arrangedToken); + UserCacheManager.UpdateUser(arrangedToken, "", + GetProviderEndPoint(), NspClientCachePath); } } - catch (Exception ex) + catch (Exception ex)//出错 重连 { - Logger.Error("启动失败:" + ex.Message, ex); - return; + if (IsStarted == false) + { StatusChanged(ClientStatus.LoginError, null); return; } + else + { + Logger.Error("启动失败:" + ex.Message, ex); + await Task.Delay(Global.ClientReconnectInterval, ONE_LIVE_TOKEN_SRC.Token); + continue; + } + } + + //0.6 从服务端获取配置 + if (ClientConfig.UseServerControl) + { + try + { + var configResult = await ClientDispatcher.GetServerClientConfig(arrangedToken); + if (configResult.State == 1) + { + if (configResult.Data == null) + { + Logger.Info("客户端请求服务端配置,但服务端没有对客户端设置配置,将继续使用客户端配置。"); + } + else + { + //ip地址和端口还原,服务端给的配置里的端口和ip地址需要作废。 + var originConfig = ClientConfig; + ClientConfig = configResult.Data; + ClientConfig.ProviderAddress = originConfig.ProviderAddress; + ClientConfig.ProviderWebPort = originConfig.ProviderWebPort; + } + } + else + { + Logger.Error("获取配置失败,远程错误:" + configResult.Msg, null); + } + } + catch (Exception ex) + { + Logger.Error("获取配置失败,本地错误:" + ex.ToString(), null); + } + } - //1.获取配置 + + //var appIdIpPortConfig = ClientConfig.Clients; + //0 获取服务器端口配置 + try + { + await InitServerPorts(); + } + catch (Exception ex)//出错 重连 + { + if (IsStarted == false) + { StatusChanged(ClientStatus.LoginError, null); return; } + else + { + Logger.Error("获取服务器端口失败:" + ex.Message, ex); + + await Task.Delay(Global.ClientReconnectInterval, ONE_LIVE_TOKEN_SRC.Token); + continue; + } + } + + //1.获取服务端分配的端口 ConnectionManager = ServerConnectionManager.Create(clientId); ConnectionManager.CurrentToken = arrangedToken; ConnectionManager.ClientGroupConnected += ServerConnnectionManager_ClientGroupConnected; @@ -150,8 +250,9 @@ public async Task Start(bool AlwaysReconnect = false) ClientModel clientModel = null;// try { - //非第一次则算作重连,发送clientid过去 + //从服务端初始化客户端配置 clientModel = await ConnectionManager.InitConfig(this.ClientConfig).ConfigureAwait(false); + complete?.Invoke(clientModel); } catch (Exception ex) { @@ -165,36 +266,37 @@ public async Task Start(bool AlwaysReconnect = false) { int counter = 0; - //1.5 写入缓存 - //File.WriteAllBytes(NSMART_CLIENT_CACHE_PATH, StringUtil.IntTo2Bytes(clientModel.ClientId)); - //2.分配配置:appid为0时说明没有分配appid,所以需要分配一个 - foreach (var app in appIdIpPortConfig) + //2.从服务端返回的appid上分配客户端的appid TODO 3 appid逻辑需要重新梳理 + foreach (var app in ClientConfig.Clients) { - if (app.AppId == 0) - { - app.AppId = clientModel.AppList[counter].AppId; - counter++; - } + //if (app.AppId == 0) + //{ + app.AppId = clientModel.AppList[counter].AppId; + counter++; + //} } Logger.Debug("****************port list*************"); List tunnelstrs = new List(); foreach (var ap in clientModel.AppList) { - var cApp = appIdIpPortConfig.First(obj => obj.AppId == ap.AppId); - var tunnelStr = ap.AppId.ToString() + ": " + ClientConfig.ProviderAddress + ":" + - ap.Port.ToString() + "=>" + - cApp.IP + ":" + cApp.TargetServicePort; + var cApp = ClientConfig.Clients.First(obj => obj.AppId == ap.AppId); + //var cApp = appIdIpPortConfig[ap.AppId]; + var tunnelStr = $@"{ap.AppId}:({cApp.Protocol}){ClientConfig.ProviderAddress}:{ap.Port}=>{cApp.IP}:{cApp.TargetServicePort}"; + + + //var tunnelStr = ap.AppId + ": " + ClientConfig.ProviderAddress + ":" + + // ap.Port + "=>" + + // cApp.IP + ":" + cApp.TargetServicePort; Logger.Debug(tunnelStr); tunnelstrs.Add(tunnelStr); } Logger.Debug("**************************************"); - ConnectionManager.PollingToProvider(StatusChanged, tunnelstrs); + _ = ConnectionManager.PollingToProvider(StatusChanged, tunnelstrs); //3.创建心跳连接 - ConnectionManager.StartHeartBeats(Global.HeartbeatInterval, HEARTBEAT_TOKEN_SRC.Token, _waiter); + _ = ConnectionManager.StartHeartBeats(Global.HeartbeatInterval, HEARTBEAT_TOKEN_SRC.Token, _waiter); IsStarted = true; - Exception exception = await _waiter.Task.ConfigureAwait(false) as Exception; - if (exception != null) + if (await _waiter.Task.ConfigureAwait(false) is Exception exception) Router.Logger.Debug($"程序异常终止:{exception.Message}。"); else Router.Logger.Debug($"未知异常。"); } @@ -210,32 +312,54 @@ public async Task Start(bool AlwaysReconnect = false) ConnectionManager.CloseAllConnections();//关闭所有连接 //出错重试 await Task.Delay(Global.ClientReconnectInterval, ONE_LIVE_TOKEN_SRC.Token); - //TODO 返回错误码 - //await Task.Delay(TimeSpan.FromHours(24), CANCEL_TOKEN.CurrentToken).ConfigureAwait(false); Router.Logger.Debug($"连接关闭,开启重试"); } //正常终止 Router.Logger.Debug($"停止重试,循环终止。"); } + + /// + /// 初始化服务器端口配置,并返回配置的dto + /// + /// + private async Task InitServerPorts() + { + var result = await ClientDispatcher.GetServerPorts(); + if (result.State == 1) + { + ClientConfig.ReversePort = result.Data.ReversePort; + ClientConfig.ConfigPort = result.Data.ConfigPort; + Router.Logger.Debug($"配置端口:反向连接端口{ClientConfig.ReversePort},配置端口{ClientConfig.ConfigPort}"); + return result.Data; + } + else + { + + throw new Exception("获取配置端口失败,服务端返回错误如下:" + result.Msg); + } + } + private async Task> Login() { string arrangedToken; int clientId; - NSPDispatcher disp = new NSPDispatcher($"{ClientConfig.ProviderAddress}:{ClientConfig.ProviderWebPort}"); - var result = await disp.LoginFromClient(CurrentLoginInfo.UserName ?? "", CurrentLoginInfo.UserPwd ?? ""); + //ClientDispatcher = new NSPDispatcher($"{ClientConfig.ProviderAddress}:{ClientConfig.ProviderWebPort}"); + var result = await ClientDispatcher.LoginFromClient(CurrentLoginInfo.UserName ?? "", CurrentLoginInfo.UserPwd ?? ""); if (result.State == 1) { Router.Logger.Debug("登录成功"); var data = result.Data; arrangedToken = data.Token; - Router.Logger.Debug($"服务端版本号:{data.Version},当前适配版本号{Global.NSmartProxyServerName}"); + Router.Logger.Debug($"服务端版本号:{data.Version},当前适配版本号{NSPVersion.NSmartProxyServerName}"); clientId = int.Parse(data.Userid); - File.WriteAllText(NSMART_CLIENT_CACHE_PATH, arrangedToken); + //File.WriteAllText(NspClientCachePath, arrangedToken); + var endPoint = ClientConfig.ProviderAddress + ":" + ClientConfig.ProviderWebPort; + UserCacheManager.UpdateUser(arrangedToken, CurrentLoginInfo.UserName, endPoint, NspClientCachePath); } else { - StatusChanged(ClientStatus.LoginError, null); + throw new Exception("登录失败,服务端返回错误如下:" + result.Msg); } @@ -248,7 +372,7 @@ private void ServerConnnectionManager_ClientGroupConnected(object sender, EventA foreach (TcpClient providerClient in args.NewClients) { Router.Logger.Debug("Open server connection."); - OpenTrasferation(args.App.AppId, providerClient); + _ = OpenTransmission(args.App.AppId, providerClient); } } @@ -271,8 +395,8 @@ public async Task Close() //服务端关闭 await NetworkUtil.ConnectAndSend( config.ProviderAddress, - config.ProviderConfigPort, - Protocol.CloseClient, + config.ConfigPort, + ServerProtocol.CloseClient, StringUtil.IntTo2Bytes(this.ConnectionManager.ClientID), true) .ConfigureAwait(false); @@ -283,107 +407,237 @@ await NetworkUtil.ConnectAndSend( } } - private async Task OpenTrasferation(int appId, TcpClient providerClient) + private async Task OpenTransmission(int appId, TcpClient providerClient) { TcpClient toTargetServer = new TcpClient(); //事件循环2 try { - byte[] buffer = new byte[1]; + byte[] buffer = new byte[10]; NetworkStream providerClientStream = providerClient.GetStream(); //接收首条消息,首条消息中返回的是appid和客户端 //消费端长连接,需要在server端保活 - try + ControlMethod controlMethod; + //TODO 5 处理应用级的keepalive + while (true) { - int readByteCount = await providerClientStream.ReadAsync(buffer, 0, buffer.Length); - if (readByteCount == 0) + try { - //抛出错误以便上层重启客户端。 - _waiter.TrySetResult(new Exception($"连接{appId}被服务器主动切断,已断开连接")); - return; + int readByteCount = await providerClientStream.ReadAsync(buffer, 0, buffer.Length); //双端标记S0001 + if (readByteCount == 0) + { + //抛出错误以便上层重启客户端。 + _waiter.TrySetResult(new Exception($"连接{appId}被服务器主动切断,已断开连接")); + return; + + } + } + catch (Exception ex) + { + //反弹连接出错为致命错误 + //此处出错后,应用程序需要重置,并重启 + _waiter.TrySetResult(ex); + throw; } - } - catch (Exception ex) - { - //反弹连接出错为致命错误 - //此处出错后,应用程序需要重置,并重启 - _waiter.TrySetResult(ex); - throw; - } - //连接后设置client为null - if (ConnectionManager.ExistClient(appId, providerClient)) - { - var removedClient = ConnectionManager.RemoveClient(appId, providerClient); - if (removedClient == false) + //TODO 4 如果是UDP则直接转发,之后返回上层 + controlMethod = (ControlMethod)buffer[0]; + + switch (controlMethod) { - Router.Logger.Debug($"没有移除{appId}任何的对象,对象不存在. hash:{providerClient.GetHashCode()}"); - return; + case ControlMethod.KeepAlive: continue; + case ControlMethod.UDPTransfer: + await OpenUdpTransmission(appId, providerClient); + continue;//udp 发送后继续循环,方法里的ConnectAppToServer会再拉起一个新连接 + case ControlMethod.TCPTransfer: + var tranferTokenId = BitConverter.ToInt32(buffer.Skip(1).Take(4).ToArray(), 0); + await OpenTcpTransmission(appId, providerClient, toTargetServer, tranferTokenId); + return;//tcp 开启隧道,并且不再利用此连接 + case ControlMethod.ForceClose: + Logger.Info("客户端在别处被抢登,当前被强制下线。"); + Close(); + return; + default: throw new Exception("非法请求:" + buffer[0]); } + } //while (controlMethod == ControlMethod.KeepAlive) ; + + } + catch (Exception ex) + { + Logger.Debug("传输时出错:" + ex); + //关闭传输连接 + toTargetServer.Close(); + providerClient.Close(); + throw; + } + + } + + private object GetUdpClientLocker = new object(); + //公用这个udpclient来发送UDP包 + //public UdpClient CurrentUdpClient = new Lazy(() => new UdpClient()).Value; + + private async Task OpenUdpTransmission(int appId, TcpClient providerClient) + { + //连接后设置client为null + if (!ClearOldClient(appId, providerClient)) return; + + Router.Logger.Debug(appId + "接收到连接请求(UDP)"); + + //TODO 4 这里有性能隐患,考虑后期改成哈希表 + ClientApp item = ClientConfig.Clients.First((obj) => obj.AppId == appId); + var networkStream = providerClient.GetStream(); + //byte[] bytesLength = new byte[2]; + // await networkStream.ReadAsync(bytesLength, 0, 2); + + // int intLength = StringUtil.DoubleBytesToInt(bytesLength); + + //TODO 8 OpenUdpTransmission + + // [method] ip(D) port buffer(D) + // [udp] X 2 X + //udp最大长度65535 + //byte[] bytesData = new byte[65535];// = new byte[intLength]; + byte[] ipByte = null; + byte[] portByte = new byte[2]; + byte[] bytesData = null; + ipByte = await networkStream.ReadNextDLengthBytes(); + await networkStream.ReadAsync(portByte, 0, 2); + bytesData = await networkStream.ReadNextDLengthBytes(); + Router.Logger.Debug($"{appId} 发送 {bytesData.Length} 字节"); + Dictionary udpDict = ConnectionManager.ConnectedUdpClients; + string endPointStr = GetEndPointStr(ipByte, portByte); + UdpClient currentUdpClient = null; + lock (GetUdpClientLocker) + { + if (!(udpDict.ContainsKey(endPointStr)) || udpDict[endPointStr].Client == null) + { + UdpClient uClient = new UdpClient(); + uClient.Connect(item.IP, item.TargetServicePort); + udpDict.Add(endPointStr, uClient); + currentUdpClient = uClient; } else { - Router.Logger.Debug($"已无法在{appId}中找到客户端 hash:{providerClient.GetHashCode()}."); - return; + currentUdpClient = udpDict[endPointStr]; } - //每移除一个链接则发起一个新的链接 - Router.Logger.Debug(appId + "接收到连接请求"); - //根据clientid_appid发送到固定的端口 - //TODO 序列没有匹配元素? - ClientApp item = ClientConfig.Clients.First((obj) => obj.AppId == appId); - - //向服务端发起一次长连接,没有接收任何外来连接请求时, - //该方法会在write处会阻塞。 - await ConnectionManager.ConnectAppToServer(appId); - Router.Logger.Debug("已建立反向连接:" + appId); - // item1:app编号,item2:ip地址,item3:目标服务端口 + } + + //发送udp包给下游,但需处理接收udp封包的情况 + await currentUdpClient.SendAsync(bytesData, bytesData.Length);//, item.IP, item.TargetServicePort); + _ = ReceiveUdpRequest(ipByte, portByte, currentUdpClient, networkStream); + + } + + private string GetEndPointStr(byte[] ipByte, byte[] portByte) + {//通过ip字节流获取ip:port串 + return Encoding.ASCII.GetString(ipByte) + ":" + StringUtil.DoubleBytesToInt(portByte).ToString(); + } + + private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + //private object udpSendLocker = new object(); + private async Task ReceiveUdpRequest(byte[] ipByte, byte[] portByte, UdpClient reciverUdpClient, NetworkStream toStatisStream) + { + //stream存在共同写入的情况 + //throw new NotImplementedException(); + while (true) + { + UdpReceiveResult udpReceiveResult + = await reciverUdpClient.ReceiveAsync();//建立超时时间,不然资源会一直占用 + // = await reciverUdpClient.ReceiveAsync(Global.ClientUdpReceiveTimeout); + //读取endpoint,必须保证如下数据包原子性 + //IPv4/IPv6(D) port returnbuffer(D) + //X 2 X + await semaphoreSlim.WaitAsync(); try { - toTargetServer.Connect(item.IP, item.TargetServicePort); + await toStatisStream.WriteDLengthBytes(ipByte); + toStatisStream.Write(portByte, 0, 2); + await toStatisStream.WriteDLengthBytes(udpReceiveResult.Buffer); } - catch + finally { - throw new Exception($"对内网服务的 {item.IP}:{item.TargetServicePort} 连接失败。"); + semaphoreSlim.Release(); } - string epString = item.IP.ToString() + ":" + item.TargetServicePort.ToString(); - Router.Logger.Debug("已连接目标服务:" + epString); - NetworkStream targetServerStream = toTargetServer.GetStream(); - //targetServerStream.Write(buffer, 0, readByteCount); - TcpTransferAsync(providerClientStream, targetServerStream, providerClient, toTargetServer, epString); - //already close connection + } + } + + private async Task OpenTcpTransmission(int appId, TcpClient providerClient, TcpClient toTargetServer, int tranferTokenId) + { + //连接后设置client为null + if (!ClearOldClient(appId, providerClient)) return; + + //每移除一个链接则发起一个新的链接 + Router.Logger.Debug(appId + "接收到连接请求(TCP)"); + //根据clientid_appid发送到固定的端口 + //TODO 4 这里有性能隐患,考虑后期改成哈希表 + ClientApp item = ClientConfig.Clients.First((obj) => obj.AppId == appId); + + //向服务端再发起另一次长连接 + await ConnectionManager.ConnectAppToServer(appId); //缓存到服务器的连接池里,下次请求使用 + Router.Logger.Debug("已建立反向连接:" + appId); + // item1:app编号,item2:ip地址,item3:目标服务端口 + try + { + toTargetServer.Connect(item.IP, item.TargetServicePort); } - catch (Exception ex) + catch { - Logger.Debug("传输时出错:" + ex); - //关闭传输连接,服务端也会相应处理,把0request发送给消费端 - //TODO ***: 连接时出错,重启客户端 - toTargetServer.Close(); - providerClient.Close(); - throw; + throw new Exception($"对内网服务的 {item.IP}:{item.TargetServicePort} 连接失败。"); + } + + string epString = item.IP.ToString() + ":" + item.TargetServicePort.ToString(); + Router.Logger.Debug("已连接目标服务:" + epString); + + //NetworkStream targetServerStream = toTargetServer.GetStream(); + //NetworkStream providerClientStream = providerClient.GetStream(); + _ = TcpTransferAsync(providerClient, toTargetServer, epString, item, tranferTokenId); + } + + private bool ClearOldClient(int appId, TcpClient providerClient) + { + if (ConnectionManager.ExistClient(appId, providerClient)) + { + var removedClient = ConnectionManager.RemoveClient(appId, providerClient); + if (removedClient == false) + { + Router.Logger.Debug($"没有移除{appId}任何的对象,对象不存在. hash:{providerClient.GetHashCode()}"); + return false; + } + } + else + { + Router.Logger.Debug($"已无法在{appId}中找到客户端 hash:{providerClient.GetHashCode()}."); + return false; } + return true; } - private async Task TcpTransferAsync(NetworkStream providerStream, NetworkStream targetServceStream, TcpClient providerClient, TcpClient toTargetServer, string epString) + private async Task TcpTransferAsync(TcpClient providerClient, TcpClient toTargetServer, string epString, ClientApp item, int tranferTokenId) { + NetworkStream targetServerStream = toTargetServer.GetStream(); + NetworkStream providerStream = providerClient.GetStream(); try { - Router.Logger.Debug("Looping start."); + string localEndPoint = providerClient.Client.LocalEndPoint.ToString(); + Router.Logger.Debug("Looping start.(" + localEndPoint + ")"); //创建相互转发流 - var taskT2PLooping = ToStaticTransfer(TRANSFERING_TOKEN_SRC.Token, targetServceStream, providerStream, epString); - var taskP2TLooping = StreamTransfer(TRANSFERING_TOKEN_SRC.Token, providerStream, targetServceStream, epString); - + var taskT2PLooping = ToStaticTransfer(TRANSFERING_TOKEN_SRC.Token, targetServerStream, providerStream, epString, item); + var taskP2TLooping = StreamTransfer(TRANSFERING_TOKEN_SRC.Token, providerStream, targetServerStream, epString, item); //close connnection,whether client or server stopped transferring. - var comletedTask = await Task.WhenAny(taskT2PLooping, taskP2TLooping); - //Router.Logger.Debug(comletedTask.Result + "传输关闭,重新读取字节"); + var completedTask = await Task.WhenAny(taskT2PLooping, taskP2TLooping); providerClient.Close(); - Router.Logger.Debug("已关闭toProvider连接。"); + Router.Logger.Debug("已关闭toProvider(" + localEndPoint + ")连接。"); toTargetServer.Close(); - Router.Logger.Debug("已关闭toTargetServer连接。"); + Router.Logger.Debug("已关闭toTargetServer(" + localEndPoint + ")连接。"); + //通知服务端关闭该连接,该消息不必等待,正常来说上面的操作已经让服务器断开了,但不排除某些网络情况,下面的操作是确保服务器断开 + Router.Logger.Debug("通知服务端关闭该连接:" + tranferTokenId.ToString()); + _ = NetworkUtil.ConnectAndSend(ClientConfig.ProviderAddress, ClientConfig.ConfigPort, ServerProtocol.Disconnet, BitConverter.GetBytes(tranferTokenId)).ConfigureAwait(false); } catch (Exception ex) { @@ -392,11 +646,33 @@ private async Task TcpTransferAsync(NetworkStream providerStream, NetworkStream } - private async Task StreamTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, string epString) + private async Task StreamTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, + string epString, ClientApp item) { + byte[] buffer = new byte[Global.ClientTunnelBufferSize]; using (fromStream) { - await fromStream.CopyToAsync(toStream, 4096, ct); + int bytesRead; + if (item.IsCompress) + { + while (!ct.IsCancellationRequested) + { + //Array.Resize(array: ref buffer, newSize: bytesRead);//此处存在copy,需要看看snappy是否支持偏移量数组 + byte[] bufferCompressed = await fromStream.ReadNextQLengthBytes(); + if (bufferCompressed.Length == 0) break; + var compressBuffer = StringUtil.DecompressInSnappy(bufferCompressed,0, bufferCompressed.Length); + bytesRead = compressBuffer.Length; + await toStream.WriteAsync(compressBuffer, 0, bytesRead, ct).ConfigureAwait(false); + } + } + else + { + while ((bytesRead = + await fromStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) + { + await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + } + } } Router.Logger.Debug($"{epString}对节点传输关闭。"); @@ -404,11 +680,31 @@ private async Task StreamTransfer(CancellationToken ct, NetworkStream fromStream } - private async Task ToStaticTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, string epString) + private async Task ToStaticTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, + string epString, ClientApp item) { + byte[] buffer = new byte[Global.ClientTunnelBufferSize]; using (fromStream) { - await fromStream.CopyToAsync(toStream, 4096, ct); + int bytesRead; + while ((bytesRead = + await fromStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) + { + if (item.IsCompress) + { + //Array.Resize(array: ref buffer, newSize: bytesRead);//此处存在copy,需要看看snappy是否支持偏移量数组 + var compressInSnappy = StringUtil.CompressInSnappy(buffer, 0, bytesRead); + //var compressedBuffer = compressInSnappy.ContentBytes; + //bytesRead = compressInSnappy.Length; + //TODO 封包传送 + if (ct.IsCancellationRequested) { Global.Logger.Info("=传输外部中止="); return; } + await toStream.WriteQLengthBytes(compressInSnappy.ContentBytes, compressInSnappy.Length).ConfigureAwait(false); + } + else + { + await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + } + } } Router.Logger.Debug($"{epString}反向链接传输关闭。"); } @@ -419,6 +715,11 @@ private void SendZero(int port) tc.Connect("127.0.0.1", port); tc.Client.Send(new byte[] { 0x00 }); } + public string GetProviderEndPoint() + { + return ClientConfig.ProviderAddress + ":" + ClientConfig.ProviderWebPort; + } + } } diff --git a/src/NSmartProxy.ClientRouter/ServerConnectionManager.cs b/src/NSmartProxy.ClientRouter/ServerConnectionManager.cs index a43d70d..7ad2463 100644 --- a/src/NSmartProxy.ClientRouter/ServerConnectionManager.cs +++ b/src/NSmartProxy.ClientRouter/ServerConnectionManager.cs @@ -11,6 +11,9 @@ using NSmartProxy.Infrastructure; using NSmartProxy.Shared; using NSmartProxy.Authorize; +using System.IO; +using NSmartProxy.Client.Authorize; +using System.Collections.Concurrent; namespace NSmartProxy.Client { @@ -26,6 +29,7 @@ public class ServerConnectionManager private int _clientID = 0; public List ConnectedConnections; + public Dictionary ConnectedUdpClients;//ip:port->udpclient public ServiceClientListCollection ServiceClientList; //key:appid value;ClientApp public Action ServerNoResponse = delegate { }; public NSPClientConfig ClientConfig; @@ -40,6 +44,7 @@ public int ClientID private ServerConnectionManager() { ConnectedConnections = new List(); + ConnectedUdpClients = new Dictionary(); Router.Logger.Debug("ServerConnectionManager initialized."); } /// @@ -48,8 +53,19 @@ private ServerConnectionManager() /// public async Task InitConfig(NSPClientConfig config) { + //TODO 7 需要重构 ClientConfig = config; - ClientModel clientModel = await ReadConfigFromProvider(); + ClientModel clientModel = null; + try + { + clientModel = await SendConfigRequest(); + } + catch (Exception ex) //如果这里出错,则自动删除缓存 + { + //TODO 2 判断服务端返回错误类型,如果是校验错误,则清空缓存 + + throw ex; + } //要求服务端分配资源并获取服务端配置 this._clientID = clientModel.ClientId; @@ -69,21 +85,22 @@ public async Task InitConfig(NSPClientConfig config) } /// - /// 从服务端读取配置 + /// 从服务端读取配置,N问一答模式 /// /// - private async Task ReadConfigFromProvider() + private async Task SendConfigRequest() { //《c#并发编程经典实例》 9.3 超时后取消 var config = ClientConfig; Router.Logger.Debug("Reading Config From Provider.."); TcpClient configClient = new TcpClient(); + configClient.NoDelay = true;//配置协议不使用nagle bool isConnected = false; bool isReconn = (this.ClientID != 0); //TODO XXX如果clientid已经分配到了id 则算作重连 - for (int j = 0; j < 3; j++) + for (int j = 0; j < 3; j++) //连接服务端 { var delayDispose = Task.Delay(TimeSpan.FromSeconds(Global.DefaultConnectTimeout)).ContinueWith(_ => configClient.Dispose()); - var connectAsync = configClient.ConnectAsync(config.ProviderAddress, config.ProviderConfigPort); + var connectAsync = configClient.ConnectAsync(config.ProviderAddress, config.ConfigPort); //超时则dispose掉 var comletedTask = await Task.WhenAny(delayDispose, connectAsync); if (!connectAsync.IsCompleted) //超时 @@ -108,8 +125,8 @@ private async Task ReadConfigFromProvider() //请求0 协议名 byte requestByte0; - if (isReconn) requestByte0 = (byte)Protocol.Reconnect;//重连则发送重连协议 - else requestByte0 = (byte)Protocol.ClientNewAppRequest; + if (isReconn) requestByte0 = (byte)ServerProtocol.Reconnect;//重连则发送重连协议 + else requestByte0 = (byte)ServerProtocol.ClientNewAppRequest; await configStream.WriteAsync(new byte[] { requestByte0 }, 0, 1); @@ -120,30 +137,69 @@ private async Task ReadConfigFromProvider() Token = CurrentToken, ClientId = this.ClientID, ClientCount = config.Clients.Count//(obj => obj.AppId == 0) //appid为0的则是未分配的 <- 取消这条规则,总是重新分配 + //Description = config.Description }.ToBytes(); await configStream.WriteAsync(requestBytes, 0, requestBytes.Length); //请求2 分配端口 - byte[] requestBytes2 = new byte[config.Clients.Count * 2]; + //httpsupport: 增加host支持 + //TODO 7固定协议实现 + //port proto option(iscompress) host description + //2 1 1 1024 96 + int oneEndpointLength = 2 + 1 + 1 + 1024 + 96;//TODO 2 临时写的,这段需要重构 + byte[] requestBytes2 = new byte[config.Clients.Count * (oneEndpointLength)]; int i = 0; foreach (var client in config.Clients) { byte[] portBytes = StringUtil.IntTo2Bytes(client.ConsumerPort); - requestBytes2[2 * i] = portBytes[0]; - requestBytes2[2 * i + 1] = portBytes[1]; + int offSetPos = oneEndpointLength * i; + requestBytes2[offSetPos] = portBytes[0]; //端口 + requestBytes2[offSetPos + 1] = portBytes[1]; //端口 + requestBytes2[offSetPos + 2] = (byte)client.Protocol;//协议 + + requestBytes2[offSetPos + 2 + 1] = (byte)(client.IsCompress ? 1 : 0); + + if (client.Host != null) //主机名 + Encoding.ASCII.GetBytes(client.Host, 0, client.Host.Length, requestBytes2, offSetPos + 4); + if (client.Description != null) + { + Encoding.UTF8.GetBytes(client.Description, 0, client.Description.Length, requestBytes2, offSetPos + 4 + 1024); + } + i++; } await configStream.WriteAndFlushAsync(requestBytes2, 0, requestBytes2.Length); - //读端口配置,此处数组的长度会限制使用的节点数(targetserver) - //如果您的机器够给力,可以调高此值 - byte[] serverConfig = new byte[256]; + //高于1500左右需要考虑分帧断包的情况 + byte[] serverConfig = new byte[1024]; + //TODO 任何read都应该设置超时 int readBytesCount = await configStream.ReadAsync(serverConfig, 0, serverConfig.Length); if (readBytesCount == 0) - Router.Logger.Debug("服务器关闭了本次连接"); + Router.Logger.Debug("服务器关闭了本次连接");//TODO 切换服务端时因为token的问题导致服务端无法使用 else if (readBytesCount == -1) Router.Logger.Debug("连接超时"); + else if (readBytesCount == 1) + { + ServerStatus status = (ServerStatus)serverConfig[0]; + if (status == ServerStatus.AuthFailed) + { + //验证失败,则删除当前服务器的缓存token + ClearLoginCache(); + Router.Logger.Debug("校验失效,已清空登录缓存"); + throw new Exception("校验失败"); + } + else if (status == ServerStatus.UserBanned) + { + Router.Logger.Debug("该用户被禁用"); + throw new Exception("该用户被禁用"); + } + else + { + Router.Logger.Debug("服务端未知异常"); + throw new Exception("服务端未知异常"); + } + } return ClientModel.GetFromBytes(serverConfig, readBytesCount); } @@ -182,12 +238,12 @@ public async Task ConnectAppToServer(int appid) byte[] requestBytes = StringUtil.ClientIDAppIdToBytes(ClientID, appid); var clientList = new List(); //补齐 - var secclient = (new TcpClient()).WrapClient(this.CurrentToken);//包装成客户端 - var client = secclient.Client; + var secureClient = (new TcpClient()).WrapClient(this.CurrentToken);//包装成客户端 + var client = secureClient.Client; try { //1.连接服务端 - var state = await secclient.ConnectWithAuthAsync(config.ProviderAddress, config.ProviderPort); + var state = await secureClient.ConnectWithAuthAsync(config.ProviderAddress, config.ReversePort); switch (state) { case AuthState.Success: @@ -222,7 +278,7 @@ public async Task ConnectAppToServer(int appid) //统一管理连接 ConnectedConnections.AddRange(clientList); - //事件循环1,这个方法必须放在最后 + //事件循环1,这个方法必须放在最后 TODO 改为复数client,优化http请求 ClientGroupConnected(this, new ClientGroupEventArgs() { NewClients = clientList, @@ -241,8 +297,10 @@ public async Task ConnectAppToServer(int appid) /// public static ServerConnectionManager Create(int clientId) { - var scm = new ServerConnectionManager(); - scm._clientID = clientId; + var scm = new ServerConnectionManager + { + _clientID = clientId + }; return scm; } @@ -321,7 +379,7 @@ public async Task StartHeartBeats(int interval, CancellationToken ct, TaskComple try { client = await NetworkUtil.ConnectAndSend(config.ProviderAddress, - config.ProviderConfigPort, Protocol.Heartbeat, StringUtil.IntTo2Bytes(this.ClientID)); + config.ConfigPort, ServerProtocol.Heartbeat, StringUtil.IntTo2Bytes(this.ClientID)); } catch (Exception ex) { @@ -376,13 +434,27 @@ public async Task StartHeartBeats(int interval, CancellationToken ct, TaskComple //TODO 重启 需要进一步关注 if (Router.TimeStamp == timeStamp) { - Router.Logger.Debug("心跳异常导致客户端重启"); - waiter.TrySetResult("心跳异常导致客户端重启"); + Router.Logger.Debug("心跳循环异常"); + waiter.TrySetResult("心跳循环异常"); } } } + //TODO 3 清除特定的登录缓存 + public void ClearLoginCache() + { + //File.Delete(Router.NspClientCachePath); + var clientUserCache = UserCacheManager.GetClientUserCache(Router.NspClientCachePath); + clientUserCache.Remove(GetEndPoint()); + UserCacheManager.SaveChanges(Router.NspClientCachePath, clientUserCache); + } + + public string GetEndPoint() + { + return ClientConfig.ProviderAddress + ":" + ClientConfig.ProviderWebPort; + } + } } diff --git a/src/NSmartProxy.Data/Config/CABoundConfig.cs b/src/NSmartProxy.Data/Config/CABoundConfig.cs new file mode 100644 index 0000000..b8441dd --- /dev/null +++ b/src/NSmartProxy.Data/Config/CABoundConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data +{ + public class CABoundConfig + { + public int ClientId; + public int AppId; + public string Path; + } +} diff --git a/src/NSmartProxy.Data/Config/NSPClientConfig.cs b/src/NSmartProxy.Data/Config/NSPClientConfig.cs index 02a7aa6..c305614 100644 --- a/src/NSmartProxy.Data/Config/NSPClientConfig.cs +++ b/src/NSmartProxy.Data/Config/NSPClientConfig.cs @@ -2,15 +2,20 @@ using System; using System.Collections.Generic; using System.Text; +using Newtonsoft.Json; namespace NSmartProxy.Data -{ +{ public class NSPClientConfig { - public int ProviderPort; //代理转发服务端口 - public int ProviderConfigPort; //配置服务端口 + [JsonIgnore] + public int ReversePort; //代理转发服务端口 + [JsonIgnore] + public int ConfigPort; //配置服务端口 + + public bool UseServerControl = true; //启用服务端配置 public string ProviderAddress; //代理服务器地址 - public int ProviderWebPort = 12309; //web管理端的端口,默认12309 //TODO 暂时写死,以后再改 + public int ProviderWebPort; //web管理端的端口,默认12309 //TODO 暂时写死,以后再改 public List Clients = new List();//客户端app } } diff --git a/src/NSmartProxy.Data/Config/NSPServerConfig.cs b/src/NSmartProxy.Data/Config/NSPServerConfig.cs index fa5168e..770be6b 100644 --- a/src/NSmartProxy.Data/Config/NSPServerConfig.cs +++ b/src/NSmartProxy.Data/Config/NSPServerConfig.cs @@ -6,10 +6,16 @@ namespace NSmartProxy.Data.Config { public class NSPServerConfig { - public int ClientServicePort = 19974; //服务端代理转发端口 - public int ConfigServicePort = 12308; //服务端配置通讯端口 + public int ReversePort = 19974; //服务端代理转发端口 + public int ConfigPort = 12308; //服务端配置通讯端口 public int WebAPIPort = 12309; //远端管理端口 + public int ReversePort_Out = 0; + public int ConfigPort_Out = 0; + public bool supportAnonymousLogin = true; - public ServerBoundConfig BoundConfig = new ServerBoundConfig(); + //[] + public ServerBoundConfig BoundConfig = new ServerBoundConfig();//用户端口绑定列表 + //public List CABoundConfigList = new List();//host->证书路径 + public Dictionary CABoundConfig = new Dictionary();//证书绑定列表 端口->证书路径,为了方便扩展这里使用字符串类型 } } diff --git a/src/NSmartProxy.Data/ServerBoundConfig.cs b/src/NSmartProxy.Data/Config/ServerBoundConfig.cs similarity index 100% rename from src/NSmartProxy.Data/ServerBoundConfig.cs rename to src/NSmartProxy.Data/Config/ServerBoundConfig.cs diff --git a/src/NSmartProxy.Data/Entites/User.cs b/src/NSmartProxy.Data/DBEntites/User.cs similarity index 90% rename from src/NSmartProxy.Data/Entites/User.cs rename to src/NSmartProxy.Data/DBEntites/User.cs index 6c5604f..d9a4605 100644 --- a/src/NSmartProxy.Data/Entites/User.cs +++ b/src/NSmartProxy.Data/DBEntites/User.cs @@ -3,7 +3,7 @@ using System.IO.Pipes; using System.Text; -namespace NSmartProxy.Data.Entity +namespace NSmartProxy.Data.DBEntities { public class User { diff --git a/src/NSmartProxy.Data/DTOs/CertDTO.cs b/src/NSmartProxy.Data/DTOs/CertDTO.cs new file mode 100644 index 0000000..39b4e72 --- /dev/null +++ b/src/NSmartProxy.Data/DTOs/CertDTO.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data.DTOs +{ + /// + /// 证书DTO + /// + public class CertDTO + { + public int Port; + public string CreateTime; + public string ToTime; + public string Hosts; + public string Extensions; + } +} diff --git a/src/NSmartProxy.Data/DTOs/FileDTO.cs b/src/NSmartProxy.Data/DTOs/FileDTO.cs index a174b59..cf1b9b4 100644 --- a/src/NSmartProxy.Data/DTOs/FileDTO.cs +++ b/src/NSmartProxy.Data/DTOs/FileDTO.cs @@ -5,9 +5,10 @@ namespace NSmartProxy.Data.DTOs { - public class FileDTO + public class FileUploadDTO { - public FileStream FileStream; + public FileInfo FileInfo; public string FileName; + //public string Host; } } diff --git a/src/NSmartProxy.Data/DTOs/FileUploadDTO.cs b/src/NSmartProxy.Data/DTOs/FileUploadDTO.cs new file mode 100644 index 0000000..a174b59 --- /dev/null +++ b/src/NSmartProxy.Data/DTOs/FileUploadDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NSmartProxy.Data.DTOs +{ + public class FileDTO + { + public FileStream FileStream; + public string FileName; + } +} diff --git a/src/NSmartProxy.Data/DTOs/LoginFormClientResult.cs b/src/NSmartProxy.Data/DTOs/LoginFormClientResult.cs index ec15dd1..7e29927 100644 --- a/src/NSmartProxy.Data/DTOs/LoginFormClientResult.cs +++ b/src/NSmartProxy.Data/DTOs/LoginFormClientResult.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace NSmartProxy.Data.DTO +namespace NSmartProxy.Data.DTOs { public class LoginFormClientResult { diff --git a/src/NSmartProxy.Data/DTOs/ServerPortsDTO.cs b/src/NSmartProxy.Data/DTOs/ServerPortsDTO.cs new file mode 100644 index 0000000..784167b --- /dev/null +++ b/src/NSmartProxy.Data/DTOs/ServerPortsDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data +{ + public class ServerPortsDTO + { + public int ReversePort = 19974; //服务端代理转发端口 + public int ConfigPort = 12308; //服务端配置通讯端口 + public int WebAPIPort = 12309; //远端管理端口 + } +} diff --git a/src/NSmartProxy.Data/DTOs/UserStatusDTO.cs b/src/NSmartProxy.Data/DTOs/UserStatusDTO.cs new file mode 100644 index 0000000..4034752 --- /dev/null +++ b/src/NSmartProxy.Data/DTOs/UserStatusDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data.DTOs +{ + public class UserStatusDTO + { + public int offlineUsersCount; + public int onlineUsersCount; + public int banUsersCount; + } +} diff --git a/src/NSmartProxy.Data/HttpResult.cs b/src/NSmartProxy.Data/HttpResult.cs index 54fbe65..555eef0 100644 --- a/src/NSmartProxy.Data/HttpResult.cs +++ b/src/NSmartProxy.Data/HttpResult.cs @@ -35,10 +35,9 @@ public static HttpResult Wrap(this T obj) public class HttpResult { - - private static HttpResult nullSuccessResult; public HttpResult() { + } //1代表成功 0代表失败 @@ -53,12 +52,6 @@ public HttpResult() /// public string Msg { get => msg; set => msg = value; } public T Data { get => data; set => data = value; } - public static HttpResult NullSuccessResult - { - get - { - return nullSuccessResult ?? new HttpResult() { state = 1 }; - } - } + public static HttpResult NullSuccessResult { get; } = new HttpResult() { state = 1 }; } } diff --git a/src/NSmartProxy.Data/Models/ClientApp.cs b/src/NSmartProxy.Data/Models/ClientApp.cs index 3a9f1fc..5d7ceab 100644 --- a/src/NSmartProxy.Data/Models/ClientApp.cs +++ b/src/NSmartProxy.Data/Models/ClientApp.cs @@ -1,10 +1,49 @@ -namespace NSmartProxy.Data +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace NSmartProxy.Data { - public class ClientApp + public enum Protocol : byte { + TCP = 0x00, + HTTP = 0x01, //同时代表HTTP/HTTPS + //HTTPS = 0x02, + UDP = 0x04 + } + + [Serializable] + public class ClientApp : ICloneable + { + [JsonIgnore] public int AppId { get; set; } public string IP { get; set; } public int TargetServicePort { get; set; } public int ConsumerPort { get; set; } + public bool IsCompress { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public Protocol Protocol { get; set; } + public string Host { get; set; } + public string Description { get; set; } + + public object Clone() + { + //返回深拷贝(不使用BinaryFormatter) + return new ClientApp + { + AppId = AppId, + IP = IP, + TargetServicePort = TargetServicePort, + ConsumerPort = ConsumerPort, + IsCompress = IsCompress, + Protocol = Protocol, + Host = Host, + Description = Description + }; + } } } diff --git a/src/NSmartProxy.Data/Models/ClientModel.cs b/src/NSmartProxy.Data/Models/ClientModel.cs index b70f8e4..995ae63 100644 --- a/src/NSmartProxy.Data/Models/ClientModel.cs +++ b/src/NSmartProxy.Data/Models/ClientModel.cs @@ -4,7 +4,7 @@ namespace NSmartProxy.Data { /// - /// 客户端,包含一个客户端的信息 + /// 客户端,包含一个客户端的信息,传输用 /// public class ClientModel: ByteSerializeableObject { @@ -38,7 +38,7 @@ public static ClientModel GetFromBytes(byte[] bytes, int totalLength = 0) int appCount = (totalLength - 2) / 3; if (((totalLength - 2) % 3) > 0) { - throw new Exception("error format"); + throw new Exception("格式错误:获取客户端对象失败"); } for (int i = 0; i < appCount; i++) { diff --git a/src/NSmartProxy.Data/Models/ClientNewAppRequest.cs b/src/NSmartProxy.Data/Models/ClientNewAppRequest.cs index 7cf9382..6b2623e 100644 --- a/src/NSmartProxy.Data/Models/ClientNewAppRequest.cs +++ b/src/NSmartProxy.Data/Models/ClientNewAppRequest.cs @@ -11,17 +11,20 @@ public class ClientNewAppRequest : ByteSerializeableObject public string Token; //TokenLength public int ClientId; //2 public int ClientCount; //1 + //public string Description;//96 public override byte[] ToBytes() { List Allbytes = new List(30); - byte[] bytes1 = System.Text.ASCIIEncoding.ASCII.GetBytes(Token); + byte[] bytes1 = System.Text.Encoding.ASCII.GetBytes(Token); byte[] bytes0 = IntTo2Bytes(bytes1.Length); byte[] bytes2 = IntTo2Bytes(ClientId); byte bytes3 = (byte)ClientCount; + //byte[] bytes4 = System.Text.Encoding.UTF8.GetBytes(Description); Allbytes.AddRange(bytes0); Allbytes.AddRange(bytes1); Allbytes.AddRange(bytes2); Allbytes.Add(bytes3); + //Allbytes.AddRange(bytes4); return Allbytes.ToArray(); } } diff --git a/src/NSmartProxy.Data/Models/ClientUserCache.cs b/src/NSmartProxy.Data/Models/ClientUserCache.cs new file mode 100644 index 0000000..11e35b4 --- /dev/null +++ b/src/NSmartProxy.Data/Models/ClientUserCache.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data.Models +{ + public class ClientUserCacheItem + { + //public string ServerEndPoint { get; set; } + public string UserName { get; set; } + //public string UserPwd { get; set; } + public string Token { get; set; } + } + + + /// + /// key服务器名 value用户 + /// + public class ClientUserCache : Dictionary + { + + } +} diff --git a/src/NSmartProxy.Data/NSmartProxy.Data.csproj b/src/NSmartProxy.Data/NSmartProxy.Data.csproj index 9f5c4f4..899c684 100644 --- a/src/NSmartProxy.Data/NSmartProxy.Data.csproj +++ b/src/NSmartProxy.Data/NSmartProxy.Data.csproj @@ -1,7 +1,11 @@ - + - netstandard2.0 + netstandard2.0;net6.0 + + + + diff --git a/src/NSmartProxy.Data/Protocol.cs b/src/NSmartProxy.Data/Protocol.cs deleted file mode 100644 index b24f9cc..0000000 --- a/src/NSmartProxy.Data/Protocol.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NSmartProxy.Data -{ - public enum Protocol : byte - { - Heartbeat = 0x01, - ClientNewAppRequest = 0x02, - Reconnect = 0x03, - CloseClient = 0x04 - } -} diff --git a/src/NSmartProxy.Data/ServerProtocol.cs b/src/NSmartProxy.Data/ServerProtocol.cs new file mode 100644 index 0000000..f3bfdbd --- /dev/null +++ b/src/NSmartProxy.Data/ServerProtocol.cs @@ -0,0 +1,25 @@ +namespace NSmartProxy.Data +{ + public enum ServerProtocol : byte + { + Heartbeat = 0x01, + ClientNewAppRequest = 0x02, + Reconnect = 0x03, + CloseClient = 0x04, + Disconnet = 0x05 + } + + /// + /// 反弹控制端口协议头 + /// + public enum ControlMethod : byte + { + TCPTransfer = 0x01, + KeepAlive = 0x03, + UDPTransfer = 0x04, + // Control = 0x05, //控制协议,用来让服务端控制客户端的配置 + Reconnect = 0x05, //重置协议,服务端发送此信号让客户端重新连接 + ForceClose = 0x6, //抢登则强制下线 + } + +} diff --git a/src/NSmartProxy.Data/ServerStatus.cs b/src/NSmartProxy.Data/ServerStatus.cs new file mode 100644 index 0000000..d47636d --- /dev/null +++ b/src/NSmartProxy.Data/ServerStatus.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Data +{ + public enum ServerStatus : byte + { + UnknowndFailed = 0x00, + Success = 0x01, + AuthFailed = 0x03, + UserBanned = 0x04 + } +} diff --git a/src/NSmartProxy.Infrastructure/ConfigHelper.cs b/src/NSmartProxy.Infrastructure/ConfigHelper.cs index 202ca93..1c7dace 100644 --- a/src/NSmartProxy.Infrastructure/ConfigHelper.cs +++ b/src/NSmartProxy.Infrastructure/ConfigHelper.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +using System.Diagnostics; +using System.Dynamic; +using Newtonsoft.Json; using NSmartProxy.Data; using System.IO; @@ -6,6 +8,26 @@ namespace NSmartProxy.Infrastructure { public static class ConfigHelper { + public static string AppSettingFullPath + { + get + { + var processModule = Process.GetCurrentProcess().MainModule; + var path1 =Path.GetDirectoryName(processModule?.FileName) + + Path.DirectorySeparatorChar + + "appsettings.json"; + var path2 = "./appsettings.json"; + if (File.Exists(path1)) + { + return path1; + } + else + { + return path2; + } + } + } + /// /// 读配置 /// @@ -38,7 +60,7 @@ public static T SaveChanges(this T config, string path) IndentChar = ' ' }; serializer.Serialize(jsonWriter, config); - + sw.Close(); } diff --git a/src/NSmartProxy/Extension/HttpServer.cs b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer.cs similarity index 58% rename from src/NSmartProxy/Extension/HttpServer.cs rename to src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer.cs index f97aa10..9e6fc12 100644 --- a/src/NSmartProxy/Extension/HttpServer.cs +++ b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +//using System.ComponentModel.DataAnnotations; using System.IO; using System.Net; using System.Net.Sockets; @@ -13,12 +13,15 @@ using NSmartProxy.Data.DTOs; using NSmartProxy.Database; using NSmartProxy.Infrastructure; +using NSmartProxy.Infrastructure.Interfaces; using NSmartProxy.Interfaces; +using NSmartProxy.Shared; + // ReSharper disable All -namespace NSmartProxy.Extension +namespace NSmartProxy.Infrastructure.Extension { - partial class HttpServer + public partial class HttpServer { #region HTTPServer @@ -26,19 +29,21 @@ partial class HttpServer public IDbOperator Dbop; private const string INDEX_PAGE = "/main.html"; - private const string BASE_FILE_PATH = "./Extension/HttpServerStaticFiles/"; - private const string BASE_LOG_FILE_PATH = "./log"; + private const string BASE_FILE_PATH = "./Web/"; + //private const string BASE_LOG_FILE_PATH = "./log"; public Dictionary FilesCache = new Dictionary(20); - public NSPServerContext ServerContext { get; } + public IServerContext ServerContext { get; } + public IWebController ControllerInstance; - public HttpServer(INSmartLogger logger, IDbOperator dbop, NSPServerContext serverContext) + public HttpServer(INSmartLogger logger, IDbOperator dbop, IServerContext serverContext, IWebController controllerInstance) { Logger = logger; Dbop = dbop; //第一次加载所有mime类型 PopulateMappings(); ServerContext = serverContext; + ControllerInstance = controllerInstance; } @@ -68,12 +73,6 @@ public async Task StartHttpService(CancellationTokenSource ctsHttp, int WebManag } Logger.Debug($"{files.Length} files cached."); - //如果库中没有任何记录,则增加默认用户 - if (Dbop.GetLength() < 1) - { - AddUserV2("admin", "admin", "1"); - } - listener.Prefixes.Add($"http://+:{WebManagementPort}/"); Logger.Debug("Listening HTTP request on port " + WebManagementPort.ToString() + "..."); await AcceptHttpRequest(listener, ctsHttp); @@ -81,12 +80,12 @@ public async Task StartHttpService(CancellationTokenSource ctsHttp, int WebManag catch (HttpListenerException ex) { Logger.Debug("Please run this program in administrator mode." + ex); - Server.Logger.Error(ex.ToString(), ex); + Logger.Error(ex.ToString(), ex); } catch (Exception ex) { Logger.Debug(ex); - Server.Logger.Error(ex.ToString(), ex); + Logger.Error(ex.ToString(), ex); } Logger.Debug("Http服务结束。"); } @@ -97,7 +96,7 @@ private async Task AcceptHttpRequest(HttpListener httpService, CancellationToken while (true) { var client = await httpService.GetContextAsync(); - ProcessHttpRequestAsync(client); + _ = ProcessHttpRequestAsync(client); } } @@ -106,12 +105,14 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) var request = context.Request; var response = context.Response; - //TODO XX 设置该同源策略为了方便调试,真实项目可以确保同源 + ControllerInstance.SetContext(context); + //context上下文设置给WebContext + //TODO XX 设置该同源策略为了方便调试,真实项目请确保同源 #if DEBUG response.AddHeader("Access-Control-Allow-Origin", "*"); #endif - + response.Headers["Server"] = ""; try { //通过request来的值进行接口调用 @@ -131,17 +132,16 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) if (!File.Exists(BASE_FILE_PATH + unit)) { - Server.Logger.Debug($"未找到文件{BASE_FILE_PATH + unit}"); + Logger.Debug($"未找到文件{BASE_FILE_PATH + unit}"); return; } //mime类型 ProcessMIME(response, unit.Substring(idx3)); //TODO 权限控制(只是控制html权限而已) - + //读文件优先去缓存读 - MemoryStream memoryStream; - if (FilesCache.TryGetValue(unit.TrimStart('/'), out memoryStream)) + if (FilesCache.TryGetValue(unit.TrimStart('/'), out MemoryStream memoryStream)) { memoryStream.Position = 0; await memoryStream.CopyToAsync(response.OutputStream); @@ -177,14 +177,14 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) MethodInfo method = null; try { - method = this.GetType().GetMethod(unit); + method = ControllerInstance.GetType().GetMethod(unit); if (method == null) { //Server.Logger.Debug($"无效的方法名{unit}"); throw new Exception($"无效的方法名{unit}"); } - //TODO 先不校验,方便调试 + //debug编译时不校验,方便调试 #if !DEBUG if (method.GetCustomAttribute() != null) { @@ -201,20 +201,23 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) if (method.GetCustomAttribute() != null) { + //返回json,类似WebAPI response.ContentType = "application/json"; - jsonObj = method.Invoke(this, parameters); + jsonObj = method.Invoke(ControllerInstance, parameters); await response.OutputStream.WriteAsync(HtmlUtil.GetContent(jsonObj.Wrap().ToJsonString())); } else if (method.GetCustomAttribute() != null) { + //返回表单页面 response.ContentType = "text/html"; - jsonObj = method.Invoke(this, parameters); + jsonObj = method.Invoke(ControllerInstance, parameters); await response.OutputStream.WriteAsync(HtmlUtil.GetContent(jsonObj.ToString())); } else if (method.GetCustomAttribute() != null) { + //验证模式 response.ContentType = "application/json"; - bool validateResult = (bool)method.Invoke(this, parameters); + bool validateResult = (bool)method.Invoke(ControllerInstance, parameters); if (validateResult == true) { await response.OutputStream.WriteAsync(HtmlUtil.GetContent("{\"valid\":true}")); @@ -226,9 +229,9 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) } else if (method.GetCustomAttribute() != null) { + //文件下载 response.ContentType = "application/octet-stream"; - FileDTO fileDto = method.Invoke(this, parameters) as FileDTO; - if (fileDto == null) + if (!(method.Invoke(ControllerInstance, parameters) is FileDTO fileDto)) { throw new Exception("文件返回失败,请查看错误日志。"); } @@ -240,9 +243,32 @@ private async Task ProcessHttpRequestAsync(HttpListenerContext context) await fileDto.FileStream.CopyToAsync(response.OutputStream); } } + else if (method.GetCustomAttribute() != null) + { + //文件上传 + response.ContentType = "application/json"; + if (request.HttpMethod.ToUpper() == "POST") + { + List paraList = new List(); + var fileName = SaveFile(request.ContentEncoding, request.ContentType, request.InputStream); + paraList.Add(new FileInfo($"./{fileName}")); + if (parameters != null) + paraList.AddRange(parameters); + jsonObj = method.Invoke(ControllerInstance, paraList.ToArray()); + await response.OutputStream.WriteAsync(HtmlUtil.GetContent(jsonObj.Wrap().ToJsonString())); + } + else + { + await response.OutputStream.WriteAsync(HtmlUtil.GetContent(HttpResult.NullSuccessResult.ToJsonString())); + } + + + //request. + } } catch (Exception ex) { + if ((ex is TargetInvocationException) && ex.InnerException != null) ex = ex.InnerException; Logger.Error(ex.Message, ex); jsonObj = new Exception(ex.Message + "---" + ex.StackTrace); response.ContentType = "application/json"; @@ -273,8 +299,7 @@ private void ProcessMIME(HttpListenerResponse response, string suffix) response.ContentEncoding = Encoding.UTF8; } - string val = ""; - if (_mimeMappings.TryGetValue(suffix, out val)) + if (_mimeMappings.TryGetValue(suffix, out string val)) { // found! response.ContentType = val; @@ -286,6 +311,117 @@ private void ProcessMIME(HttpListenerResponse response, string suffix) } + + + #region 文件读取 + private String GetBoundary(String ctype) + { + return "--" + ctype.Split(';')[1].Split('=')[1]; + } + + private string SaveFile(Encoding enc, String contentType, Stream input) + { + + Byte[] boundaryBytes = enc.GetBytes(GetBoundary(contentType)); + Int32 boundaryLen = boundaryBytes.Length; + string fileName = Guid.NewGuid().ToString("N") + ".temp"; + using (FileStream output = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + { + Byte[] buffer = new Byte[1024]; + Int32 len = input.Read(buffer, 0, 1024); + Int32 startPos = -1; + + // Find start boundary + while (true) + { + if (len == 0) + { + throw new Exception("Start Boundaray Not Found"); + } + + startPos = IndexOf(buffer, len, boundaryBytes); + if (startPos >= 0) + { + break; + } + else + { + Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen); + len = input.Read(buffer, boundaryLen, 1024 - boundaryLen); + } + } + + // Skip four lines (Boundary, Content-Disposition, Content-Type, and a blank) + for (Int32 i = 0; i < 4; i++) + { + while (true) + { + if (len == 0) + { + throw new Exception("Preamble not Found."); + } + + startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos); + if (startPos >= 0) + { + startPos++; + break; + } + else + { + len = input.Read(buffer, 0, 1024); + } + } + } + + Array.Copy(buffer, startPos, buffer, 0, len - startPos); + len = len - startPos; + + while (true) + { + Int32 endPos = IndexOf(buffer, len, boundaryBytes); + if (endPos >= 0) + { + if (endPos > 0) output.Write(buffer, 0, endPos - 2); + break; + } + else if (len <= boundaryLen) + { + throw new Exception("End Boundaray Not Found"); + } + else + { + output.Write(buffer, 0, len - boundaryLen); + //每次放置后40个字节到首部,读取接下来984个字节,在此基础上进行byte查找,绝妙! + Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen); + len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen; + } + } + } + + return fileName; + } + + private Int32 IndexOf(Byte[] buffer, Int32 len, Byte[] boundaryBytes) + { + for (Int32 i = 0; i <= len - boundaryBytes.Length; i++) + { + Boolean match = true; + for (Int32 j = 0; j < boundaryBytes.Length && match; j++) + { + match = buffer[i + j] == boundaryBytes[j]; + } + + if (match) + { + return i; + } + } + + return -1; + } + #endregion + #endregion } diff --git a/src/NSmartProxy/Extension/HttpServerAttributes.cs b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServerAttributes.cs similarity index 77% rename from src/NSmartProxy/Extension/HttpServerAttributes.cs rename to src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServerAttributes.cs index 2d16b06..663ddb0 100644 --- a/src/NSmartProxy/Extension/HttpServerAttributes.cs +++ b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServerAttributes.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace NSmartProxy.Extension +namespace NSmartProxy.Infrastructure.Extension { /// @@ -29,6 +29,15 @@ public class FileAPIAttribute : Attribute } + /// + /// 代表上传文件接口,第一个函数必须是File + /// + public class FileUploadAttribute : Attribute + { + + } + + /// /// 代表接口需要校验权限 /// diff --git a/src/NSmartProxy/Extension/HttpServer_MIMEs.cs b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer_MIMEs.cs similarity index 99% rename from src/NSmartProxy/Extension/HttpServer_MIMEs.cs rename to src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer_MIMEs.cs index d2bf3b4..5257d89 100644 --- a/src/NSmartProxy/Extension/HttpServer_MIMEs.cs +++ b/src/NSmartProxy.Infrastructure/Extensions/HttpServer/HttpServer_MIMEs.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace NSmartProxy.Extension +namespace NSmartProxy.Infrastructure.Extension { partial class HttpServer { diff --git a/src/NSmartProxy.Infrastructure/Extensions/StreamExtension.cs b/src/NSmartProxy.Infrastructure/Extensions/StreamExtension.cs index 37d4fb4..a8b6f3c 100644 --- a/src/NSmartProxy.Infrastructure/Extensions/StreamExtension.cs +++ b/src/NSmartProxy.Infrastructure/Extensions/StreamExtension.cs @@ -1,5 +1,10 @@ -using System.IO; +using System; +using System.IO; +using System.Net.Security; using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading.Tasks; using NSmartProxy.Shared; @@ -15,6 +20,25 @@ public static async Task WriteAndFlushAsync(this Stream stream, byte[] buffer, i await stream.FlushAsync(); } + + /// + /// 带超时的readasync,timeout 毫秒 + /// + /// + /// + /// + /// + /// + /// + public static async Task ReceiveAsync(this UdpClient client, int timeOut) + { + UdpReceiveResult? udpReceiveResult = null; + var receiveTask = Task.Run(async () => { udpReceiveResult = await client.ReceiveAsync(); }); + var isReceived = await Task.WhenAny(receiveTask, Task.Delay(timeOut)) == receiveTask; + if (!isReceived) return null; + return udpReceiveResult; + } + /// /// 带超时的readasync,timeout 毫秒 /// @@ -24,7 +48,7 @@ public static async Task WriteAndFlushAsync(this Stream stream, byte[] buffer, i /// /// /// - public static async Task ReadAsync(this NetworkStream stream, byte[] buffer, int offset, int count, int timeOut) + public static async Task ReadAsync(this Stream stream, byte[] buffer, int offset, int count, int timeOut) { var receiveCount = 0; var receiveTask = Task.Run(async () => { receiveCount = await stream.ReadAsync(buffer, offset, count); }); @@ -33,10 +57,163 @@ public static async Task ReadAsync(this NetworkStream stream, byte[] buffer return receiveCount; } - - public static async Task ReadAsyncEx(this NetworkStream stream, byte[] buffer) + /// + /// 读取接下来N字节的定长数据,如果服务端没有发那么多信息, + /// 可能会出现读不全的情况,也有可能出现阻塞超时的情况 + /// + public static async Task ReadNextSTLengthBytes(this Stream stream, byte[] buffer) + { + int restBufferLength = buffer.Length; + int totalReceivedBytes = 0; + while (restBufferLength > 0) + { + int receivedBytes = await stream.ReadAsyncEx(buffer, totalReceivedBytes, restBufferLength); + if (receivedBytes <= 0) return -1;//没有接收满则断开返回-1 + totalReceivedBytes += receivedBytes; + restBufferLength -= receivedBytes; + } + return totalReceivedBytes; + } + + public static async Task ReadAsyncEx(this Stream stream, byte[] buffer, int offset, int count) + { + return await stream.ReadAsync(buffer, offset, count, Global.DefaultConnectTimeout); + } + + public static async Task ReadAsyncEx(this Stream stream, byte[] buffer) + { + return await stream.ReadAsyncEx(buffer, 0, buffer.Length); + } + + public static Stream ProcessSSL(this Stream clientStream, X509Certificate cert) + { + try + { + SslStream sslStream = new SslStream(clientStream); + sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls12, true); + sslStream.ReadTimeout = 10000; + sslStream.WriteTimeout = 10000; + return sslStream; + } + catch (Exception ex) + { + clientStream.Close(); + throw ex; + } + + //return null; + } + + public static async Task WriteAsync(this Stream stream, byte[] bytes) + { + await stream.WriteAsync(bytes, 0, bytes.Length); + } + + /// + /// 写入字符串(ASCII) + /// + /// + /// + /// + public static async Task WriteDLengthBytes(this Stream stream, string asciiStr) + { + byte[] bytes = Encoding.ASCII.GetBytes(asciiStr); + stream.Write(StringUtil.IntTo2Bytes(bytes.Length), 0, 2); + await stream.WriteAsync(bytes); + } + /// + /// 写入动态长度的字节,头两字节存放长度 + /// + /// + /// + /// + public static async Task WriteDLengthBytes(this Stream stream, byte[] bytes) + { + stream.Write(StringUtil.IntTo2Bytes(bytes.Length), 0, 2); + await stream.WriteAsync(bytes); + } + + /// + /// 写入动态长度的字节,头四字节存放长度 + /// + /// + /// + /// + public static async Task WriteQLengthBytes(this Stream stream, byte[] bytes,int forceLength = -1) + { + if (forceLength > 0) + { + stream.Write(StringUtil.IntTo4Bytes(forceLength), 0, 4); + await stream.WriteAsync(bytes,0,forceLength); + } + else + { + stream.Write(StringUtil.IntTo4Bytes(bytes.Length), 0, 4); + await stream.WriteAsync(bytes); + } + + } + + /// + /// 读取动态长度的字节,头两字节存放长度 + /// + /// + /// + /// + public static async Task ReadNextDLengthBytes(this Stream stream) { - return await stream.ReadAsync(buffer,0,buffer.Length,Global.DefaultConnectTimeout); + // int readInt = 0; + byte[] bt2 = new byte[2]; + //readInt += bt2.Length; + var readByte = await stream.ReadAsync(bt2, 0, 2); + byte[] bytes = null; + if (readByte > 0) + { + int length = BitConverter.ToInt16(bt2, 0); + bytes = new byte[length]; + await stream.ReadAsync(bytes, 0, length); + } + return bytes; } + + /// + /// 读取动态长度的字节,头四字节存放长度,这种方式最大支持2G的数据 + /// 这种比ReadNextDLengthBytes支持更大数据,但是读取时候会造成中断,需要多次读取 + /// + /// + /// + /// + public static async Task ReadNextQLengthBytes(this Stream stream) + { + byte[] bt2 = new byte[4]; + var readByte = await stream.ReadAsync(bt2, 0, 4); + byte[] bytes = null; + if (readByte > 0) + { + int length = BitConverter.ToInt32(bt2, 0); + bytes = new byte[length]; + int readedByteCount = await stream.ReadAsync(bytes, 0, length); + if (readedByteCount == 0) + { + return new byte[0]; + } + int restLength = length - readedByteCount; + while (restLength > 0) + { + readedByteCount += await stream.ReadAsync(bytes, readedByteCount, restLength); + if (readedByteCount == 0) + { + return new byte[0]; + } + restLength = length - readedByteCount; + } + } + else + { + return new byte[0]; + } + return bytes; + } + } } \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/Extensions/TcpPipelinesExt.cs b/src/NSmartProxy.Infrastructure/Extensions/TcpPipelinesExt.cs new file mode 100644 index 0000000..93510ea --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Extensions/TcpPipelinesExt.cs @@ -0,0 +1,163 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace NSmartProxy.Infrastructure +{ + public static class TcpPipelinesExt + { + private static async Task ProcessLinesAsync(this Socket socket, Action> proc) + { + Console.WriteLine($"[{socket.RemoteEndPoint}]: connected"); + var pipe = new Pipe(); + Task writing = FillPipeAsync(socket, pipe.Writer); + Task reading = ReadPipeAsync(socket, pipe.Reader, proc); + + await Task.WhenAll(reading, writing); + Console.WriteLine($"[{socket.RemoteEndPoint}]: disconnected"); + } + + private static async Task FillPipeAsync(Socket socket, PipeWriter writer) + { + const int minimumBufferSize = 512; + + while (true) + { + try + { + // 从pipeWriter请求512字节长度的内存 + Memory memory = writer.GetMemory(minimumBufferSize); + + int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None); + if (bytesRead == 0) + { + break; + } + + // 移动游标到bytesRead的位置 + writer.Advance(bytesRead); + } + catch + { + break; + } + + // 清空writer缓冲区 + FlushResult result = await writer.FlushAsync(); + + if (result.IsCompleted) + { + break; + } + } + + // 标记writer完成 + writer.Complete(); + } + + private static async Task ReadPipeAsync(Socket socket, PipeReader reader, Action> proc) + { + while (true) + { + ReadResult result = await reader.ReadAsync(); + + ReadOnlySequence buffer = result.Buffer; + SequencePosition? position = null; + + do + { + // Find the EOL + position = buffer.PositionOf((byte)'\n'); + + if (position != null) + { + var line = buffer.Slice(0, position.Value); + ProcessLine(socket, line, proc); + + // 获取下一个内存位置 + var next = buffer.GetPosition(1, position.Value); + + // 游标前进到下一个字节的位置 \n + buffer = buffer.Slice(next); + } + } + while (position != null); + + // 移动游标到buffer.start,并且读取到buffer.end + reader.AdvanceTo(buffer.Start, buffer.End); + + if (result.IsCompleted) + { + break; + } + } + + //标记reader完成 + reader.Complete(); + } + + private static void ProcessLine(Socket socket, in ReadOnlySequence buffer, Action> proc) + { + if (proc != null) + { + foreach (var segment in buffer) + { + proc(segment); + } + } + } + + public static Task ReceiveAsync(this Socket socket, Memory memory, SocketFlags socketFlags) + { + var arraySegment = GetArray(memory); + return SocketTaskExtensions.ReceiveAsync(socket, arraySegment, socketFlags); + } + + private static ArraySegment GetArray(ReadOnlyMemory memory) + { + if (!MemoryMarshal.TryGetArray(memory, out var result)) + { + throw new InvalidOperationException("Buffer backed by array was expected"); + } + + return result; + } + +#if NET461 + internal static class Extensions + { + public static Task ReceiveAsync(this Socket socket, Memory memory, SocketFlags socketFlags) + { + var arraySegment = GetArray(memory); + return SocketTaskExtensions.ReceiveAsync(socket, arraySegment, socketFlags); + } + + public static string GetString(this Encoding encoding, ReadOnlyMemory memory) + { + var arraySegment = GetArray(memory); + return encoding.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + } + + private static ArraySegment GetArray(Memory memory) + { + return GetArray((ReadOnlyMemory)memory); + } + + private static ArraySegment GetArray(ReadOnlyMemory memory) + { + if (!MemoryMarshal.TryGetArray(memory, out var result)) + { + throw new InvalidOperationException("Buffer backed by array was expected"); + } + + return result; + } + } +#endif + } +} diff --git a/src/NSmartProxy.Infrastructure/I18N.cs b/src/NSmartProxy.Infrastructure/I18N.cs new file mode 100644 index 0000000..811a939 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/I18N.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; + +namespace NSmartProxy.Infrastructure +{ + public class I18N + { + private const string BASE_PATH = "i18n"; + private static string originFullPath; + private static string lFullPath; + public static Dictionary LMap; + public static string L(string lStr) + { + if (LMap.ContainsKey(lStr)) + { + return LMap[lStr]; + } + else + { + LMap.Add(lStr, lStr); + File.AppendAllLines(originFullPath, new string[] { lStr }); + return lStr; + } + } + + static I18N() + { + + LMap = new Dictionary(); + string originLFile = "ol.txt"; + //debug模式,提取代码中的多语言字段 + string entryPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\" + BASE_PATH + "\\"; + string langSign = System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag.ToLowerInvariant(); + originFullPath = entryPath + originLFile; + + + if (langSign.StartsWith("zh")) + { + lFullPath = entryPath + "zh.txt"; + } + else + { + lFullPath = entryPath + "en.txt"; + } + + if (!Directory.Exists(entryPath)) + { + Directory.CreateDirectory(entryPath); + } + if (!File.Exists(originFullPath)) + { + File.Create(originFullPath).Close(); + } + if (!File.Exists(lFullPath)) + { + File.Create(lFullPath).Close(); + } + + //读取所有多语言文件 TODO 先暂时只读取原始文件 + var lstrs = File.ReadAllLines(lFullPath); + var ostrs = File.ReadAllLines(originFullPath); + for (var i = 0; i < ostrs.Length; i++) + { + if (i > lstrs.Length - 1) + { + LMap[ostrs[i]] = ostrs[i]; + File.AppendAllLines(lFullPath, new string[] { ostrs[i] });//补齐多的行 + } + else + { + LMap[ostrs[i]] = lstrs[i]; + } + } + } + } +} diff --git a/src/NSmartProxy.Infrastructure/Interfaces/IHttpServerContext.cs b/src/NSmartProxy.Infrastructure/Interfaces/IHttpServerContext.cs new file mode 100644 index 0000000..5aa6989 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Interfaces/IHttpServerContext.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NSmartProxy.Infrastructure.Interfaces +{ + public interface IServerContext + { + } +} diff --git a/src/NSmartProxy.Infrastructure/Interfaces/IWebController.cs b/src/NSmartProxy.Infrastructure/Interfaces/IWebController.cs new file mode 100644 index 0000000..1e75bf3 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Interfaces/IWebController.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace NSmartProxy.Infrastructure.Interfaces +{ + public interface IWebController + { + void SetContext(HttpListenerContext context); + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/BaseChannel.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/BaseChannel.cs new file mode 100644 index 0000000..c734413 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/BaseChannel.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace LiteNetLib +{ + internal abstract class BaseChannel + { + public BaseChannel Next; + protected readonly NetPeer Peer; + protected readonly Queue OutgoingQueue; + + protected BaseChannel(NetPeer peer) + { + Peer = peer; + OutgoingQueue = new Queue(64); + } + + public int PacketsInQueue + { + get { return OutgoingQueue.Count; } + } + + public void AddToQueue(NetPacket packet) + { + lock (OutgoingQueue) + OutgoingQueue.Enqueue(packet); + } + + public abstract void SendNextPackets(); + public abstract bool ProcessPacket(NetPacket packet); + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/ConnectionRequest.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/ConnectionRequest.cs new file mode 100644 index 0000000..edfca03 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/ConnectionRequest.cs @@ -0,0 +1,119 @@ +using System.Net; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public enum ConnectionRequestType + { + Incoming, + PeerToPeer + } + + internal enum ConnectionRequestResult + { + None, + Accept, + Reject + } + + internal interface IConnectionRequestListener + { + void OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length); + } + + public class ConnectionRequest + { + private readonly IConnectionRequestListener _listener; + private int _used; + + public IPEndPoint RemoteEndPoint { get { return Peer.EndPoint; } } + public readonly NetDataReader Data; + public ConnectionRequestType Type { get; private set; } + + internal ConnectionRequestResult Result { get; private set; } + internal readonly long ConnectionId; + internal readonly byte ConnectionNumber; + internal readonly NetPeer Peer; + + //类似悲观锁定?? + private bool TryActivate() + { + return Interlocked.CompareExchange(ref _used, 1, 0) == 0; + } + + internal ConnectionRequest( + long connectionId, + byte connectionNumber, + ConnectionRequestType type, + NetDataReader netDataReader, + NetPeer peer, + IConnectionRequestListener listener) + { + ConnectionId = connectionId; + ConnectionNumber = connectionNumber; + Type = type; + Peer = peer; + Data = netDataReader; + _listener = listener; + } + + public NetPeer AcceptIfKey(string key) + { + if (!TryActivate()) + return null; + try + { + if (Data.GetString() == key) + { + Result = ConnectionRequestResult.Accept; + _listener.OnConnectionSolved(this, null, 0, 0); + return Peer; + } + } + catch + { + NetDebug.WriteError("[AC] Invalid incoming data"); + } + Result = ConnectionRequestResult.Reject; + _listener.OnConnectionSolved(this, null, 0, 0); + return null; + } + + /// + /// Accept connection and get new NetPeer as result + /// + /// Connected NetPeer + public NetPeer Accept() + { + if (!TryActivate()) + return null; + Result = ConnectionRequestResult.Accept; + _listener.OnConnectionSolved(this, null, 0, 0); + return Peer; + } + + public void Reject(byte[] rejectData, int start, int length) + { + if (!TryActivate()) + return; + Result = ConnectionRequestResult.Reject; + _listener.OnConnectionSolved(this, rejectData, start, length); + } + + public void Reject() + { + Reject(null, 0, 0); + } + + public void Reject(byte[] rejectData) + { + Reject(rejectData, 0, rejectData.Length); + } + + public void Reject(NetDataWriter rejectData) + { + Reject(rejectData.Data, 0, rejectData.Length); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/INetEventListener.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/INetEventListener.cs new file mode 100644 index 0000000..725a758 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/INetEventListener.cs @@ -0,0 +1,225 @@ +using System.Net; +using System.Net.Sockets; + +namespace LiteNetLib +{ + /// + /// Type of message that you receive in OnNetworkReceiveUnconnected event + /// + public enum UnconnectedMessageType + { + BasicMessage, + Broadcast + } + + /// + /// Disconnect reason that you receive in OnPeerDisconnected event + /// + public enum DisconnectReason + { + ConnectionFailed, + Timeout, + HostUnreachable, + NetworkUnreachable, + RemoteConnectionClose, + DisconnectPeerCalled, + ConnectionRejected, + InvalidProtocol, + UnknownHost + } + + /// + /// Additional information about disconnection + /// + public struct DisconnectInfo + { + /// + /// Additional info why peer disconnected + /// + public DisconnectReason Reason; + + /// + /// Error code (if reason is SocketSendError or SocketReceiveError) + /// + public SocketError SocketErrorCode; + + /// + /// Additional data that can be accessed (only if reason is RemoteConnectionClose) + /// + public NetPacketReader AdditionalData; + } + + public interface INetEventListener + { + /// + /// New remote peer connected to host, or client connected to remote host + /// + /// Connected peer object + void OnPeerConnected(NetPeer peer); + + /// + /// Peer disconnected + /// + /// disconnected peer + /// additional info about reason, errorCode or data received with disconnect message + void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); + + /// + /// Network error (on send or receive) + /// + /// From endPoint (can be null) + /// Socket error + void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + + /// + /// Received some data + /// + /// From peer + /// DataReader containing all received data + /// Type of received packet + void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod); + + /// + /// Received unconnected message + /// + /// From address (IP and Port) + /// Message data + /// Message type (simple, discovery request or responce) + void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + + /// + /// Latency information updated + /// + /// Peer with updated latency + /// latency value in milliseconds + void OnNetworkLatencyUpdate(NetPeer peer, int latency); + + /// + /// On peer connection requested + /// + /// Request information (EndPoint, internal id, additional data) + void OnConnectionRequest(ConnectionRequest request); + } + + public interface IDeliveryEventListener + { + /// + /// On reliable message delivered + /// + /// + /// + void OnMessageDelivered(NetPeer peer, object userData); + } + + /// + /// ί+ģʽãһѵlistener + /// + public class EventBasedNetListener : INetEventListener, IDeliveryEventListener + { + public delegate void OnPeerConnected(NetPeer peer); + public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo); + public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError); + public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod); + public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType); + public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency); + public delegate void OnConnectionRequest(ConnectionRequest request); + public delegate void OnDeliveryEvent(NetPeer peer, object userData); + + public event OnPeerConnected PeerConnectedEvent; + public event OnPeerDisconnected PeerDisconnectedEvent; + public event OnNetworkError NetworkErrorEvent; + public event OnNetworkReceive NetworkReceiveEvent; + public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent; + public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent; + public event OnConnectionRequest ConnectionRequestEvent; + public event OnDeliveryEvent DeliveryEvent; + + public void ClearPeerConnectedEvent() + { + PeerConnectedEvent = null; + } + + public void ClearPeerDisconnectedEvent() + { + PeerDisconnectedEvent = null; + } + + public void ClearNetworkErrorEvent() + { + NetworkErrorEvent = null; + } + + public void ClearNetworkReceiveEvent() + { + NetworkReceiveEvent = null; + } + + public void ClearNetworkReceiveUnconnectedEvent() + { + NetworkReceiveUnconnectedEvent = null; + } + + public void ClearNetworkLatencyUpdateEvent() + { + NetworkLatencyUpdateEvent = null; + } + + public void ClearConnectionRequestEvent() + { + ConnectionRequestEvent = null; + } + + public void ClearDeliveryEvent() + { + DeliveryEvent = null; + } + + void INetEventListener.OnPeerConnected(NetPeer peer) + { + if (PeerConnectedEvent != null) + PeerConnectedEvent(peer); + } + + void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) + { + if (PeerDisconnectedEvent != null) + PeerDisconnectedEvent(peer, disconnectInfo); + } + + void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) + { + if (NetworkErrorEvent != null) + NetworkErrorEvent(endPoint, socketErrorCode); + } + + void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) + { + if (NetworkReceiveEvent != null) + NetworkReceiveEvent(peer, reader, deliveryMethod); + } + + void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) + { + if (NetworkReceiveUnconnectedEvent != null) + NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType); + } + + void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) + { + if (NetworkLatencyUpdateEvent != null) + NetworkLatencyUpdateEvent(peer, latency); + } + + void INetEventListener.OnConnectionRequest(ConnectionRequest request) + { + if (ConnectionRequestEvent != null) + ConnectionRequestEvent(request); + } + + void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData) + { + if (DeliveryEvent != null) + DeliveryEvent(peer, userData); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/Crc32cLayer.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/Crc32cLayer.cs new file mode 100644 index 0000000..716c4b5 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/Crc32cLayer.cs @@ -0,0 +1,36 @@ +using LiteNetLib.Utils; +using System; + +namespace LiteNetLib.Layers +{ + public sealed class Crc32cLayer : PacketLayerBase + { + public Crc32cLayer() : base(CRC32C.ChecksumSize) + { + + } + + public override void ProcessInboundPacket(ref byte[] data, ref int length) + { + if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize) + { + NetDebug.WriteError("[NM] DataReceived size: bad!"); + return; + } + + int checksumPoint = length - CRC32C.ChecksumSize; + if (CRC32C.Compute(data, 0, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint)) + { + NetDebug.Write("[NM] DataReceived checksum: bad!"); + return; + } + length -= CRC32C.ChecksumSize; + } + + public override void ProcessOutBoundPacket(ref byte[] data, ref int offset, ref int length) + { + FastBitConverter.GetBytes(data, length, CRC32C.Compute(data, offset, length)); + length += CRC32C.ChecksumSize; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/PacketLayerBase.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/PacketLayerBase.cs new file mode 100644 index 0000000..264a418 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/PacketLayerBase.cs @@ -0,0 +1,15 @@ +namespace LiteNetLib.Layers +{ + public abstract class PacketLayerBase + { + public readonly int ExtraPacketSizeForLayer; + + protected PacketLayerBase(int extraPacketSizeForLayer) + { + ExtraPacketSizeForLayer = extraPacketSizeForLayer; + } + + public abstract void ProcessInboundPacket(ref byte[] data, ref int length); + public abstract void ProcessOutBoundPacket(ref byte[] data, ref int offset, ref int length); + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/XorEncryptLayer.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/XorEncryptLayer.cs new file mode 100644 index 0000000..82ac9a4 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Layers/XorEncryptLayer.cs @@ -0,0 +1,59 @@ +using System; +using System.Text; + +namespace LiteNetLib.Layers +{ + public class XorEncryptLayer : PacketLayerBase + { + private byte[] _byteKey; + + public XorEncryptLayer() : base(0) + { + + } + + public XorEncryptLayer(byte[] key) : this() + { + SetKey(key); + } + + public XorEncryptLayer(string key) : this() + { + SetKey(key); + } + + public void SetKey(string key) + { + _byteKey = Encoding.UTF8.GetBytes(key); + } + + public void SetKey(byte[] key) + { + if (_byteKey == null || _byteKey.Length != key.Length) + _byteKey = new byte[key.Length]; + Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length); + } + + public override void ProcessInboundPacket(ref byte[] data, ref int length) + { + if (_byteKey == null) + return; + for (var i = 0; i < length; i++) + { + var offset = i % _byteKey.Length; + data[i] = (byte)(data[i] ^ _byteKey[offset]); + } + } + + public override void ProcessOutBoundPacket(ref byte[] data, ref int offset, ref int length) + { + if (_byteKey == null) + return; + var cur = offset; + for (var i = 0; i < length; i++, cur++) + { + data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]); + } + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NatPunchModule.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NatPunchModule.cs new file mode 100644 index 0000000..c3a0d44 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NatPunchModule.cs @@ -0,0 +1,244 @@ +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using LiteNetLib.Utils; + +//Some code parts taken from lidgren-network-gen3 +namespace LiteNetLib +{ + public interface INatPunchListener + { + void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token,byte HostOrClient); + void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token); + } + + public class EventBasedNatPunchListener : INatPunchListener + { + public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token, byte HostOrClient); + public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token); + + public event OnNatIntroductionRequest NatIntroductionRequest; + public event OnNatIntroductionSuccess NatIntroductionSuccess; + + void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token, byte HostOrClient) + { + if(NatIntroductionRequest != null) + NatIntroductionRequest(localEndPoint, remoteEndPoint, token, HostOrClient); + } + + void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token) + { + if (NatIntroductionSuccess != null) + NatIntroductionSuccess(targetEndPoint, token); + } + } + + /// + /// udpר + /// Module for UDP NAT Hole punching operations. Can be accessed from NetManager + /// + public sealed class NatPunchModule + { + struct RequestEventData + { + public IPEndPoint LocalEndPoint; + public IPEndPoint RemoteEndPoint; + public string Token; + public byte HostOrCilent; + } + + struct SuccessEventData + { + public IPEndPoint TargetEndPoint; + public string Token; + } + + private readonly NetSocket _socket; + private readonly Queue _requestEvents; + private readonly Queue _successEvents; + public const byte HostByte = 1; + public const byte ClientByte = 0; + public const int MaxTokenLength = 256; + + private INatPunchListener _natPunchListener; + + internal NatPunchModule(NetSocket socket) + { + _socket = socket; + _requestEvents = new Queue(); + _successEvents = new Queue(); + } + + public void Init(INatPunchListener listener) + { + _natPunchListener = listener; + } + + //natʹͨģ˷ֱclientExternalhostExternalͱ + public void NatIntroduce( + IPEndPoint hostInternal, + IPEndPoint hostExternal, + IPEndPoint clientInternal, + IPEndPoint clientExternal, + string additionalInfo) + { + NetDataWriter dw = new NetDataWriter(); + + //First packet (server) + //send to client + dw.Put((byte)PacketProperty.NatIntroduction); + dw.Put(ClientByte); + dw.Put(hostInternal); + dw.Put(hostExternal); + dw.Put(additionalInfo, MaxTokenLength); + SocketError errorCode = 0; + _socket.SendTo(dw.Data, 0, dw.Length, clientExternal, ref errorCode); + + //Second packet (client) + //send to server + dw.Reset(); + dw.Put((byte)PacketProperty.NatIntroduction); + dw.Put(HostByte); + dw.Put(clientInternal); + dw.Put(clientExternal); + dw.Put(additionalInfo, MaxTokenLength); + _socket.SendTo(dw.Data, 0, dw.Length, hostExternal, ref errorCode); + } + + public void PollEvents() + { + if (_natPunchListener == null) + return; + lock (_successEvents) + { + while (_successEvents.Count > 0) + { + var evt = _successEvents.Dequeue(); + _natPunchListener.OnNatIntroductionSuccess(evt.TargetEndPoint, evt.Token); + } + } + lock (_requestEvents) + { + while (_requestEvents.Count > 0) + { + var evt = _requestEvents.Dequeue(); + _natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token,evt.HostOrCilent); + } + } + } + + public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo, byte hostByte) + { + //prepare outgoing data + NetDataWriter dw = new NetDataWriter(); + string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4); + if (string.IsNullOrEmpty(networkIp)) + { + networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6); + } + IPEndPoint localEndPoint = NetUtils.MakeEndPoint(networkIp, _socket.LocalPort); + dw.Put((byte)PacketProperty.NatIntroductionRequest); + dw.Put(localEndPoint); + dw.Put(additionalInfo, MaxTokenLength); + dw.Put(hostByte);//ӻֹߵӻת + //prepare packet + SocketError errorCode = 0; + _socket.SendTo(dw.Data, 0, dw.Length, masterServerEndPoint, ref errorCode); + } + + private void HandleNatPunch(IPEndPoint senderEndPoint, NetDataReader dr) + { + byte fromHostByte = dr.GetByte(); + if (fromHostByte != HostByte && fromHostByte != ClientByte) + { + //garbage + return; + } + + //Read info + string additionalInfo = dr.GetString(MaxTokenLength); + NetDebug.Write(NetLogLevel.Trace, "[NAT] punch received from {0} - additional info: {1}", senderEndPoint, additionalInfo); + + //Release punch success to client; enabling him to Connect() to msg.Sender if token is ok + lock (_successEvents) + { + _successEvents.Enqueue(new SuccessEventData { TargetEndPoint = senderEndPoint, Token = additionalInfo }); + } + } + + private void HandleNatIntroduction(NetDataReader dr) + { + // read intro + byte hostByte = dr.GetByte(); + IPEndPoint remoteInternal = dr.GetNetEndPoint(); + IPEndPoint remoteExternal = dr.GetNetEndPoint(); + string token = dr.GetString(MaxTokenLength); + + NetDebug.Write(NetLogLevel.Trace, "[NAT] introduction received; we are designated " + (hostByte == HostByte ? "host" : "client")); + NetDataWriter writer = new NetDataWriter(); + + SocketError errorCode = 0; + // send internal punch + writer.Put((byte)PacketProperty.NatPunchMessage); + writer.Put(hostByte); + writer.Put(token); + _socket.SendTo(writer.Data, 0, writer.Length, remoteInternal, ref errorCode); + NetDebug.Write(NetLogLevel.Trace, "[NAT] internal punch sent to " + remoteInternal); + + // send external punch + writer.Reset(); + writer.Put((byte)PacketProperty.NatPunchMessage); + writer.Put(hostByte); + writer.Put(token); + if (hostByte == HostByte)//ttl + { + _socket.Ttl = 2; + _socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode); + _socket.Ttl = NetConstants.SocketTTL; + } + else + { + _socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode); + } + + NetDebug.Write(NetLogLevel.Trace, "[NAT] external punch sent to " + remoteExternal); + } + + private void HandleNatIntroductionRequest(IPEndPoint senderEndPoint, NetDataReader dr) + { + IPEndPoint localEp = dr.GetNetEndPoint(); + string token = dr.GetString(MaxTokenLength); + byte hostByte = dr.GetByte(); + lock (_requestEvents) + { + _requestEvents.Enqueue(new RequestEventData + { + LocalEndPoint = localEp, + RemoteEndPoint = senderEndPoint, + Token = token, + HostOrCilent = hostByte + }); + } + } + + internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet) + { + var dr = new NetDataReader(packet.RawData, NetConstants.HeaderSize, packet.Size); + switch (packet.Property) + { + case PacketProperty.NatIntroductionRequest: + //We got request and must introduce + HandleNatIntroductionRequest(senderEndPoint, dr); + break; + case PacketProperty.NatIntroduction: + //We got introduce and must punch + HandleNatIntroduction(dr); + break; + case PacketProperty.NatPunchMessage: + //We got punch and can connect + HandleNatPunch(senderEndPoint, dr); + break; + } + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetConstants.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetConstants.cs new file mode 100644 index 0000000..520c2c4 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetConstants.cs @@ -0,0 +1,72 @@ +namespace LiteNetLib +{ + /// + /// Sending method type + /// + public enum DeliveryMethod : byte + { + /// + /// Unreliable. Packets can be dropped, can be duplicated, can arrive without order. + /// + Unreliable = 4, + + /// + /// Reliable. Packets won't be dropped, won't be duplicated, can arrive without order. + /// + ReliableUnordered = 0, + + /// + /// Unreliable. Packets can be dropped, won't be duplicated, will arrive in order. + /// + Sequenced = 1, + + /// + /// Reliable and ordered. Packets won't be dropped, won't be duplicated, will arrive in order. + /// + ReliableOrdered = 2, + + /// + /// Reliable only last packet. Packets can be dropped (except the last one), won't be duplicated, will arrive in order. + /// + ReliableSequenced = 3 + } + + /// + /// 常量,在实际情况可以调校这些常量. + /// + public static class NetConstants + { + //can be tuned + public const int DefaultWindowSize = 64; + public const int SocketBufferSize = 1024 * 1024; //1mb + public const int SocketTTL = 255; + + public const int HeaderSize = 1; + public const int ChanneledHeaderSize = 4; + public const int FragmentHeaderSize = 6; + public const int FragmentTotalSize = ChanneledHeaderSize + FragmentHeaderSize; + public const ushort MaxSequence = 32768; + public const ushort HalfMaxSequence = MaxSequence / 2; + + //protocol + internal const int ProtocolId = 10; + internal const int MaxUdpHeaderSize = 68; + + internal static readonly int[] PossibleMtu = + { + 576 - MaxUdpHeaderSize, //minimal + 1232 - MaxUdpHeaderSize, + 1460 - MaxUdpHeaderSize, //google cloud + 1472 - MaxUdpHeaderSize, //VPN + 1492 - MaxUdpHeaderSize, //Ethernet with LLC and SNAP, PPPoE (RFC 1042) + 1500 - MaxUdpHeaderSize //Ethernet II (RFC 1191) + }; + + internal static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1]; + + //peer specific + public const byte MaxConnectionNumber = 4; + + public const int PacketPoolSize = 1000; + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetDebug.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetDebug.cs new file mode 100644 index 0000000..4d9b8e5 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetDebug.cs @@ -0,0 +1,78 @@ +using System; +using System.Diagnostics; + +namespace LiteNetLib +{ + public enum NetLogLevel + { + Warning, + Error, + Trace, + Info + } + + /// + /// Interface to implement for your own logger + /// + public interface INetLogger + { + void WriteNet(NetLogLevel level, string str, params object[] args); + } + + /// + /// Static class for defining your own LiteNetLib logger instead of Console.WriteLine + /// or Debug.Log if compiled with UNITY flag + /// + public static class NetDebug + { + public static INetLogger Logger = null; + private static readonly object DebugLogLock = new object(); + private static void WriteLogic(NetLogLevel logLevel, string str, params object[] args) + { + lock (DebugLogLock) + { + if (Logger == null) + { +#if UNITY_4 || UNITY_5 || UNITY_5_3_OR_NEWER + UnityEngine.Debug.Log(string.Format(str, args)); +#else + Console.WriteLine(str, args); +#endif + } + else + { + Logger.WriteNet(logLevel, str, args); + } + } + } + + [Conditional("DEBUG")] + internal static void Write(string str, params object[] args) + { + WriteLogic(NetLogLevel.Trace, str, args); + } + + [Conditional("DEBUG")] + internal static void Write(NetLogLevel level, string str, params object[] args) + { + WriteLogic(level, str, args); + } + + [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] + internal static void WriteForce(string str, params object[] args) + { + WriteLogic(NetLogLevel.Trace, str, args); + } + + [Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")] + internal static void WriteForce(NetLogLevel level, string str, params object[] args) + { + WriteLogic(level, str, args); + } + + internal static void WriteError(string str, params object[] args) + { + WriteLogic(NetLogLevel.Error, str, args); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetExceptions.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetExceptions.cs new file mode 100644 index 0000000..db999a6 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetExceptions.cs @@ -0,0 +1,33 @@ +using System; +namespace LiteNetLib +{ + public class InvalidPacketException: ArgumentException + { + public InvalidPacketException() + { + } + + public InvalidPacketException(string message): base(message) + { + } + + public InvalidPacketException(string message, Exception innerException): base(message, innerException) + { + } + } + + public class TooBigPacketException : InvalidPacketException + { + public TooBigPacketException() + { + } + + public TooBigPacketException(string message) : base(message) + { + } + + public TooBigPacketException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetManager.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetManager.cs new file mode 100644 index 0000000..9783a07 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetManager.cs @@ -0,0 +1,1508 @@ +#if DEBUG +#define STATS_ENABLED +#endif + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + public class NetPacketReader : NetDataReader + { + private NetPacket _packet; + private readonly NetManager _manager; + private readonly NetEvent _evt; + + internal NetPacketReader(NetManager manager, NetEvent evt) + { + _manager = manager; + _evt = evt; + } + + internal void SetSource(NetPacket packet) + { + if (packet == null) + return; + _packet = packet; + SetSource(packet.RawData, packet.GetHeaderSize(), packet.Size); + } + + public void Recycle() + { + Clear(); + if (_packet != null) + _manager.NetPacketPool.Recycle(_packet); + _packet = null; + _manager.RecycleEvent(_evt); + } + } + + internal sealed class NetEvent + { + public enum EType + { + Connect, + Disconnect, + Receive, + ReceiveUnconnected, + Error, + ConnectionLatencyUpdated, + Broadcast, + ConnectionRequest, + MessageDelivered + } + public EType Type; + + public NetPeer Peer; + public IPEndPoint RemoteEndPoint; + public object UserData; + public int Latency; + public SocketError ErrorCode; + public DisconnectReason DisconnectReason; + public ConnectionRequest ConnectionRequest; + public DeliveryMethod DeliveryMethod; + public readonly NetPacketReader DataReader; + + public NetEvent(NetManager manager) + { + DataReader = new NetPacketReader(manager, this); + } + } + + /// + /// ࣬ͬʱڷ˿ɿͻ + /// + public class NetManager : INetSocketListener, IConnectionRequestListener, IEnumerable + { + private class IPEndPointComparer : IEqualityComparer + { + public bool Equals(IPEndPoint x, IPEndPoint y) + { + return x.Equals(y); + } + + public int GetHashCode(IPEndPoint obj) + { + return obj.GetHashCode(); + } + } + + public struct NetPeerEnumerator : IEnumerator + { + private readonly NetPeer _initialPeer; + private NetPeer _p; + + public NetPeerEnumerator(NetPeer p) + { + _initialPeer = p; + _p = null; + } + + public void Dispose() + { + + } + + public bool MoveNext() + { + _p = _p == null ? _initialPeer : _p.NextPeer; + return _p != null; + } + + public void Reset() + { + throw new NotSupportedException(); + } + + public NetPeer Current + { + get { return _p; } + } + + object IEnumerator.Current + { + get { return _p; } + } + } + +#if DEBUG + private struct IncomingData + { + public byte[] Data; + public IPEndPoint EndPoint; + public DateTime TimeWhenGet; + } + private readonly List _pingSimulationList = new List(); + private readonly Random _randomGenerator = new Random(); + private const int MinLatencyThreshold = 5; +#endif + + private readonly NetSocket _socket; + private Thread _logicThread; + + private readonly Queue _netEventsQueue; + private readonly Stack _netEventsPool; + private readonly INetEventListener _netEventListener; + private readonly IDeliveryEventListener _deliveryEventListener; + + private readonly Dictionary _peersDict; + private readonly ReaderWriterLockSlim _peersLock; + private volatile NetPeer _headPeer; + private volatile int _connectedPeersCount; + private readonly List _connectedPeerListCache; + private int _lastPeerId; + private readonly Queue _peerIds; + private byte _channelsCount = 1; + + internal readonly NetPacketPool NetPacketPool; + + //config section + /// + /// Enable messages receiving without connection. (with SendUnconnectedMessage method) + /// + public bool UnconnectedMessagesEnabled = false; + + /// + /// Enable nat punch messages + /// + public bool NatPunchEnabled = false; + + /// + /// Library logic update and send period in milliseconds + /// + public int UpdateTime = 15; + + /// + /// Interval for latency detection and checking connection + /// + public int PingInterval = 1000; + + /// + /// If NetManager doesn't receive any packet from remote peer during this time then connection will be closed + /// (including library internal keepalive packets) + /// + public int DisconnectTimeout = 5000; + + /// + /// Simulate packet loss by dropping random amout of packets. (Works only in DEBUG mode) + /// + public bool SimulatePacketLoss = false; + + /// + /// ģӳ + /// Simulate latency by holding packets for random time. (Works only in DEBUG mode) + /// + public bool SimulateLatency = false; + + /// + /// Chance of packet loss when simulation enabled. value in percents (1 - 100). + /// + public int SimulationPacketLossChance = 10; + + /// + /// Minimum simulated latency + /// + public int SimulationMinLatency = 30; + + /// + /// ӳģ⣬ԲĹ + /// Maximum simulated latency + /// + public int SimulationMaxLatency = 100; + + /// + /// Experimental feature. Events automatically will be called without PollEvents method from another thread + /// + public bool UnsyncedEvents = false; + + /// + /// If true - delivery event will be called from "receive" thread otherwise on PollEvents call + /// + public bool UnsyncedDeliveryEvent = false; + + /// + /// Allows receive broadcast packets + /// + public bool BroadcastReceiveEnabled = false; + + /// + /// Delay betwen initial connection attempts + /// + public int ReconnectDelay = 500; + + /// + /// Maximum connection attempts before client stops and call disconnect event. + /// ӳԴ + /// + public int MaxConnectAttempts = 1; + + /// + /// Enables socket option "ReuseAddress" for specific purposes + /// + public bool ReuseAddress = false; + + /// + /// Statistics of all connections + /// + public readonly NetStatistics Statistics; + + //modules + /// + /// NatPunchModule for NAT hole punching operations + /// + public readonly NatPunchModule NatPunchModule; + + /// + /// Returns true if socket listening and update thread is running + /// + public bool IsRunning { get; private set; } + + /// + /// Local EndPoint (host and port) + /// + public int LocalPort { get { return _socket.LocalPort; } } + + /// + /// ֮Զ + /// Automatically recycle NetPacketReader after OnReceive event + /// + public bool AutoRecycle; + + /// + /// IPv6 support + /// + public bool IPv6Enabled = true; + + /// + /// First peer. Useful for Client mode + /// + public NetPeer FirstPeer + { + get { return _headPeer; } + } + + /// + /// QoS channel count per message type (value must be between 1 and 64 channels) + /// + public byte ChannelsCount + { + get { return _channelsCount; } + set + { + if(value < 1 || value > 64) + throw new ArgumentException("Channels count must be between 1 and 64"); + _channelsCount = value; + } + } + + /// + /// Returns connected peers list (with internal cached list) + /// + public List ConnectedPeerList + { + get + { + _connectedPeerListCache.Clear(); + for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & ConnectionState.Connected) != 0) + _connectedPeerListCache.Add(netPeer); + } + return _connectedPeerListCache; + } + } + + /// + /// Returns connected peers count + /// + public int PeersCount { get { return _connectedPeersCount; } } + + private bool TryGetPeer(IPEndPoint endPoint, out NetPeer peer) + { + _peersLock.EnterReadLock(); + bool result = _peersDict.TryGetValue(endPoint, out peer); + _peersLock.ExitReadLock(); + return result; + } + + private NetPeer TryAddPeer(NetPeer peer) + { + _peersLock.EnterUpgradeableReadLock(); + NetPeer existingPeer; + if (_peersDict.TryGetValue(peer.EndPoint, out existingPeer)) + { + //return back unused peerId + lock (_peerIds) + _peerIds.Enqueue(peer.Id); + + _peersLock.ExitUpgradeableReadLock(); + return existingPeer; + } + _peersLock.EnterWriteLock(); + if (_headPeer != null) + { + peer.NextPeer = _headPeer; + _headPeer.PrevPeer = peer; + } + _headPeer = peer; + _peersDict.Add(peer.EndPoint, peer); + _peersLock.ExitWriteLock(); + _peersLock.ExitUpgradeableReadLock(); + return peer; + } + + private void RemovePeer(NetPeer peer) + { + _peersLock.EnterWriteLock(); + RemovePeerInternal(peer); + _peersLock.ExitWriteLock(); + } + + private void RemovePeerInternal(NetPeer peer) + { + if (!_peersDict.Remove(peer.EndPoint)) + return; + if (peer == _headPeer) + _headPeer = peer.NextPeer; + + if (peer.PrevPeer != null) + peer.PrevPeer.NextPeer = peer.NextPeer; + if (peer.NextPeer != null) + peer.NextPeer.PrevPeer = peer.PrevPeer; + peer.PrevPeer = null; + + lock (_peerIds) + _peerIds.Enqueue(peer.Id); + } + + /// + /// NetManager constructor + /// + /// Network events listener (also can implement IDeliveryEventListener) + public NetManager(INetEventListener listener) + { + _socket = new NetSocket(this); + _netEventListener = listener; + _deliveryEventListener = listener as IDeliveryEventListener; + _netEventsQueue = new Queue(); + _netEventsPool = new Stack(); + NetPacketPool = new NetPacketPool(); + NatPunchModule = new NatPunchModule(_socket); + Statistics = new NetStatistics(); + _connectedPeerListCache = new List(); + _peersDict = new Dictionary(new IPEndPointComparer()); + _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + _peerIds = new Queue(); + } + + internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) + { + CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency); + } + + internal void MessageDelivered(NetPeer fromPeer, object userData) + { + if(_deliveryEventListener != null) + CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData); + } + + internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint) + { + var result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); + NetPacketPool.Recycle(packet); + return result; + } + + internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) + { + return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); + } + + internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + if (!IsRunning) + return 0; + + SocketError errorCode = 0; + int result = _socket.SendTo(message, start, length, remoteEndPoint, ref errorCode); + NetPeer fromPeer; + switch (errorCode) + { + case SocketError.MessageSize: + NetDebug.Write(NetLogLevel.Trace, "[SRD] 10040, datalen: {0}", length); + return -1; + case SocketError.HostUnreachable: + if (TryGetPeer(remoteEndPoint, out fromPeer)) + DisconnectPeerForce(fromPeer, DisconnectReason.HostUnreachable, errorCode, null); + CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: errorCode); + return -1; + case SocketError.NetworkUnreachable: + if (TryGetPeer(remoteEndPoint, out fromPeer)) + DisconnectPeerForce(fromPeer, DisconnectReason.NetworkUnreachable, errorCode, null); + CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: errorCode); + return -1; + case SocketError.ConnectionReset: //connection reset (connection closed) + if (TryGetPeer(remoteEndPoint, out fromPeer)) + DisconnectPeerForce(fromPeer, DisconnectReason.RemoteConnectionClose, errorCode, null); + return -1; + } + if (result <= 0) + return 0; +#if STATS_ENABLED + Statistics.PacketsSent++; + Statistics.BytesSent += (uint)length; +#endif + return result; + } + + internal void DisconnectPeerForce(NetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + NetPacket eventData) + { + DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData); + } + + private void DisconnectPeer( + NetPeer peer, + DisconnectReason reason, + SocketError socketErrorCode, + bool force, + byte[] data, + int start, + int count, + NetPacket eventData) + { + bool wasConnected = peer.ConnectionState == ConnectionState.Connected; + if (!peer.Shutdown(data, start, count, force)) + return; + if(wasConnected) + _connectedPeersCount--; + CreateEvent( + NetEvent.EType.Disconnect, + peer, + errorCode: socketErrorCode, + disconnectReason: reason, + readerSource: eventData); + } + + private void CreateEvent( + NetEvent.EType type, + NetPeer peer = null, + IPEndPoint remoteEndPoint = null, + SocketError errorCode = 0, + int latency = 0, + DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, + ConnectionRequest connectionRequest = null, + DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, + NetPacket readerSource = null, + object userData = null) + { + NetEvent evt = null; + bool unsyncEvent = UnsyncedEvents; + + if (type == NetEvent.EType.Connect) + _connectedPeersCount++; + else if (type == NetEvent.EType.MessageDelivered) + unsyncEvent = UnsyncedDeliveryEvent; + + lock (_netEventsPool) + { + if (_netEventsPool.Count > 0) + evt = _netEventsPool.Pop(); + } + if(evt == null) + evt = new NetEvent(this); + evt.Type = type; + evt.DataReader.SetSource(readerSource); + evt.Peer = peer; + evt.RemoteEndPoint = remoteEndPoint; + evt.Latency = latency; + evt.ErrorCode = errorCode; + evt.DisconnectReason = disconnectReason; + evt.ConnectionRequest = connectionRequest; + evt.DeliveryMethod = deliveryMethod; + evt.UserData = userData; + + if (unsyncEvent) + { + ProcessEvent(evt); + } + else + { + lock (_netEventsQueue) + _netEventsQueue.Enqueue(evt); + } + } + + private void ProcessEvent(NetEvent evt) + { + NetDebug.Write("[NM] Processing event: " + evt.Type); + bool emptyData = evt.DataReader.IsNull; + switch (evt.Type) + { + case NetEvent.EType.Connect: + _netEventListener.OnPeerConnected(evt.Peer); + break; + case NetEvent.EType.Disconnect: + var info = new DisconnectInfo + { + Reason = evt.DisconnectReason, + AdditionalData = evt.DataReader, + SocketErrorCode = evt.ErrorCode + }; + _netEventListener.OnPeerDisconnected(evt.Peer, info); + break; + case NetEvent.EType.Receive: + _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.DeliveryMethod); + break; + case NetEvent.EType.ReceiveUnconnected: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); + break; + case NetEvent.EType.Broadcast: + _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast); + break; + case NetEvent.EType.Error: + _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); + break; + case NetEvent.EType.ConnectionLatencyUpdated: + _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); + break; + case NetEvent.EType.ConnectionRequest: + _netEventListener.OnConnectionRequest(evt.ConnectionRequest); + break; + case NetEvent.EType.MessageDelivered: + _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData); + break; + } + //Recycle if not message + if (emptyData) + RecycleEvent(evt); + else if (AutoRecycle) + evt.DataReader.Recycle(); + } + + internal void RecycleEvent(NetEvent evt) + { + evt.Peer = null; + evt.ErrorCode = 0; + evt.RemoteEndPoint = null; + evt.ConnectionRequest = null; + lock (_netEventsPool) + _netEventsPool.Push(evt); + } + + //Update function + private void UpdateLogic() + { + var peersToRemove = new List(); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + while (IsRunning) + { +#if DEBUG + if (SimulateLatency) + { + var time = DateTime.UtcNow; + lock (_pingSimulationList) + { + for (int i = 0; i < _pingSimulationList.Count; i++) + { + var incomingData = _pingSimulationList[i]; + if (incomingData.TimeWhenGet <= time) + { + DataReceived(incomingData.Data, incomingData.Data.Length, incomingData.EndPoint); + _pingSimulationList.RemoveAt(i); + i--; + } + } + } + } +#endif + +#if STATS_ENABLED + ulong totalPacketLoss = 0; +#endif + int elapsed = (int)stopwatch.ElapsedMilliseconds; + if (elapsed <= 0) + elapsed = 1; + for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) + { + peersToRemove.Add(netPeer); + } + else + { + netPeer.Update(elapsed); +#if STATS_ENABLED + totalPacketLoss += netPeer.Statistics.PacketLoss; +#endif + } + } + if (peersToRemove.Count > 0) + { + _peersLock.EnterWriteLock(); + for (int i = 0; i < peersToRemove.Count; i++) + RemovePeerInternal(peersToRemove[i]); + _peersLock.ExitWriteLock(); + peersToRemove.Clear(); + } +#if STATS_ENABLED + Statistics.PacketLoss = totalPacketLoss; +#endif + int sleepTime = UpdateTime - (int)(stopwatch.ElapsedMilliseconds - elapsed); + stopwatch.Reset(); + stopwatch.Start(); + if (sleepTime > 0) + Thread.Sleep(sleepTime); + } + stopwatch.Stop(); + } + + void INetSocketListener.OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint) + { + if (errorCode != 0) + { + CreateEvent(NetEvent.EType.Error, errorCode: errorCode); + NetDebug.WriteError("[NM] Receive error: {0}", errorCode); + return; + } +#if DEBUG + if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) + { + //drop packet + return; + } + if (SimulateLatency) + { + int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); + if (latency > MinLatencyThreshold) + { + byte[] holdedData = new byte[length]; + Buffer.BlockCopy(data, 0, holdedData, 0, length); + + lock (_pingSimulationList) + { + _pingSimulationList.Add(new IncomingData + { + Data = holdedData, + EndPoint = remoteEndPoint, + TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency) + }); + } + //hold packet + return; + } + } +#endif + try + { + //ProcessEvents + DataReceived(data, length, remoteEndPoint); + } + catch(Exception e) + { + //protects socket receive thread + NetDebug.WriteError("[NM] SocketReceiveThread error: " + e ); + } + } + + void IConnectionRequestListener.OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length) + { + if (request.Result == ConnectionRequestResult.Reject) + { + NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); + request.Peer.Reject(request.ConnectionId, request.ConnectionNumber, rejectData, start, length); + } + else + { + //Accept + request.Peer.Accept(request.ConnectionId, request.ConnectionNumber); + //TODO: sync + //Add event + CreateEvent(NetEvent.EType.Connect, request.Peer); + + NetDebug.Write(NetLogLevel.Trace, "[NM] Received peer connection Id: {0}, EP: {1}", + request.Peer.ConnectTime, request.Peer.EndPoint); + } + } + + private int GetNextPeerId() + { + lock (_peerIds) + { + if (_peerIds.Count == 0) + return _lastPeerId++; + return _peerIds.Dequeue(); + } + } + + private void ProcessConnectRequest( + IPEndPoint remoteEndPoint, + NetPeer netPeer, + NetConnectRequestPacket connRequest) + { + byte connectionNumber = connRequest.ConnectionNumber; + + //if we have peer + if (netPeer != null) + { + NetDebug.Write("ConnectRequest LastId: {0}, NewId: {1}, EP: {2}", netPeer.ConnectTime, connRequest.ConnectionTime, remoteEndPoint); + var processResult = netPeer.ProcessConnectRequest(connRequest); + switch (processResult) + { + case ConnectRequestResult.Reconnection: + DisconnectPeerForce(netPeer, DisconnectReason.RemoteConnectionClose, 0, null); + RemovePeer(netPeer); + //go to new connection + break; + case ConnectRequestResult.NewConnection: + RemovePeer(netPeer); + //go to new connection + break; + case ConnectRequestResult.P2PConnection: + CreateEvent( + NetEvent.EType.ConnectionRequest, + connectionRequest: new ConnectionRequest( + netPeer.ConnectTime, + connectionNumber, + ConnectionRequestType.PeerToPeer, + connRequest.Data, + netPeer, + this) + ); + return; + default: + //no operations needed + return; + } + //ConnectRequestResult.NewConnection + //Set next connection number + connectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + //To reconnect peer + } + else + { + NetDebug.Write("ConnectRequest Id: {0}, EP: {1}", connRequest.ConnectionTime, remoteEndPoint); + } + //Add new peer and craete ConnectRequest event + NetDebug.Write("[NM] Creating request event: " + connRequest.ConnectionTime); + netPeer = new NetPeer(this, remoteEndPoint, GetNextPeerId()); + if (TryAddPeer(netPeer) == netPeer) + { + CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: new ConnectionRequest( + connRequest.ConnectionTime, + connectionNumber, + ConnectionRequestType.Incoming, + connRequest.Data, + netPeer, + this)); + } + } + + private void DataReceived(byte[] reusableBuffer, int count, IPEndPoint remoteEndPoint) + { +#if STATS_ENABLED + Statistics.PacketsReceived++; + Statistics.BytesReceived += (uint) count; +#endif + //Try read packet + NetPacket packet = NetPacketPool.GetPacket(count, false); + if (!packet.FromBytes(reusableBuffer, 0, count)) + { + NetPacketPool.Recycle(packet); + NetDebug.WriteError("[NM] DataReceived: bad!"); + return; + } + + //get peer + //Check normal packets + NetPeer netPeer; + //old packets protection + bool peerFound = TryGetPeer(remoteEndPoint, out netPeer); + + //Check unconnected + switch (packet.Property) + { + case PacketProperty.PeerNotFound: + if (peerFound) + { + if (netPeer.ConnectionState != ConnectionState.Connected) + return; + if (packet.Size == 1) + { + //first reply + var p = NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound, 9); + p.RawData[1] = 0; + FastBitConverter.GetBytes(p.RawData, 2, netPeer.ConnectTime); + SendRawAndRecycle(p, remoteEndPoint); + NetDebug.Write("PeerNotFound sending connectId: {0}", netPeer.ConnectTime); + } + else if (packet.Size == 10 && packet.RawData[1] == 1 && BitConverter.ToInt64(packet.RawData, 2) == netPeer.ConnectTime) + { + //second reply + NetDebug.Write("PeerNotFound received our connectId: {0}", netPeer.ConnectTime); + DisconnectPeerForce(netPeer, DisconnectReason.RemoteConnectionClose, 0, null); + } + } + else if (packet.Size == 10 && packet.RawData[1] == 0) + { + //send reply back + packet.RawData[1] = 1; + SendRawAndRecycle(packet, remoteEndPoint); + } + break; + case PacketProperty.Broadcast: + if (!BroadcastReceiveEnabled) + break; + CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet); + break; + + case PacketProperty.UnconnectedMessage: + if (!UnconnectedMessagesEnabled) + break; + CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet); + break; + + case PacketProperty.NatIntroduction: + case PacketProperty.NatIntroductionRequest: + case PacketProperty.NatPunchMessage: + if (NatPunchEnabled) + NatPunchModule.ProcessMessage(remoteEndPoint, packet); + break; + case PacketProperty.InvalidProtocol: + if (peerFound && netPeer.ConnectionState == ConnectionState.Outcoming) + DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null); + break; + case PacketProperty.Disconnect: + if (peerFound) + { + var disconnectResult = netPeer.ProcessDisconnect(packet); + if (disconnectResult == DisconnectResult.None) + { + NetPacketPool.Recycle(packet); + return; + } + DisconnectPeerForce( + netPeer, + disconnectResult == DisconnectResult.Disconnect + ? DisconnectReason.RemoteConnectionClose + : DisconnectReason.ConnectionRejected, + 0, packet); + } + else + { + NetPacketPool.Recycle(packet); + } + //Send shutdown + SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.ShutdownOk, 0), remoteEndPoint); + break; + + case PacketProperty.ConnectAccept: + var connAccept = NetConnectAcceptPacket.FromData(packet); + if (connAccept != null && peerFound && netPeer.ProcessConnectAccept(connAccept)) + CreateEvent(NetEvent.EType.Connect, netPeer); + break; + case PacketProperty.ConnectRequest: + if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) + { + SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.InvalidProtocol, 0), remoteEndPoint); + break; + } + var connRequest = NetConnectRequestPacket.FromData(packet); + if (connRequest != null) + ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); + break; + default: + if(peerFound) + netPeer.ProcessPacket(packet); + else + SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound, 0), remoteEndPoint); + break; + } + } + + internal void ReceiveFromPeer(NetPacket packet, NetPeer fromPeer) + { + var deliveryMethod = packet.Property == PacketProperty.Channeled + ? (DeliveryMethod) (packet.ChannelId % 4) + : DeliveryMethod.Unreliable; + CreateEvent(NetEvent.EType.Receive, fromPeer, deliveryMethod: deliveryMethod, readerSource: packet); + } + #region Ⱥ + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(NetDataWriter writer, DeliveryMethod options) + { + SendToAll(writer.Data, 0, writer.Length, options); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, DeliveryMethod options) + { + SendToAll(data, 0, data.Length, options); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) + { + SendToAll(data, start, length, 0, options); + } + + /// + /// Send data to all connected peers + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) + { + SendToAll(writer.Data, 0, writer.Length, channelNumber, options); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) + { + SendToAll(data, 0, data.Length, channelNumber, options); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) + { + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Send(data, start, length, channelNumber, options); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, 0, data.Length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, start, length, 0, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); + } + + /// + /// Send data to all connected peers + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); + } + + + /// + /// Send data to all connected peers + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// Excluded peer + public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) + { + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if (netPeer != excludePeer) + netPeer.Send(data, start, length, channelNumber, options); + } + } + + #endregion Ⱥ + + /// + /// Start logic thread and listening on available port + /// + public bool Start() + { + return Start(0); + } + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) + { + if (IsRunning) + return false; + if (!_socket.Bind(addressIPv4, addressIPv6, port, ReuseAddress, IPv6Enabled)) + return false; + IsRunning = true; + _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; + _logicThread.Start(); + return true; + } + + /// + /// Start logic thread and listening on selected port + /// + /// bind to specific ipv4 address + /// bind to specific ipv6 address + /// port to listen + public bool Start(string addressIPv4, string addressIPv6, int port) + { + IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); + IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); + return Start(ipv4, ipv6, port); + } + + /// + /// Start logic thread and listening on selected port + /// + /// port to listen + public bool Start(int port) + { + return Start(IPAddress.Any, IPAddress.IPv6Any, port); + } + + /// + /// Send message without connection + /// + /// Raw data + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) + { + return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); + } + + /// + /// Send message without connection + /// + /// Data serializer + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) + { + return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); + } + + /// + /// Send message without connection + /// + /// Raw data + /// data start + /// data length + /// Packet destination + /// Operation result + public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) + { + if (!IsRunning) + return false; + var packet = NetPacketPool.GetWithData(PacketProperty.UnconnectedMessage, message, start, length); + bool result = SendRawAndRecycle(packet, remoteEndPoint) > 0; + return result; + } + + public bool SendBroadcast(NetDataWriter writer, int port) + { + return SendBroadcast(writer.Data, 0, writer.Length, port); + } + + public bool SendBroadcast(byte[] data, int port) + { + return SendBroadcast(data, 0, data.Length, port); + } + + public bool SendBroadcast(byte[] data, int start, int length, int port) + { + if (!IsRunning) + return false; + var packet = NetPacketPool.GetWithData(PacketProperty.Broadcast, data, start, length); + bool result = _socket.SendBroadcast(packet.RawData, 0, packet.Size, port); + NetPacketPool.Recycle(packet); + return result; + } + + /// + /// Flush all queued packets of all peers + /// + public void Flush() + { + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Flush(); + } + + /// + /// Receive all pending events. Call this in game update code + /// ѭе¼,˾ + /// + public void PollEvents() + { + if (UnsyncedEvents) + return; + while (true) + { + NetEvent evt; + lock (_netEventsQueue) + { + if (_netEventsQueue.Count > 0) + evt = _netEventsQueue.Dequeue(); + else + return; + } + ProcessEvent(evt); + } + } + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected + /// Manager is not running. Call + public NetPeer Connect(string address, int port, string key) + { + return Connect(address, port, NetDataWriter.FromString(key)); + } + + /// + /// Connect to remote host + /// + /// Server IP or hostname + /// Server Port + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected + /// Manager is not running. Call + public NetPeer Connect(string address, int port, NetDataWriter connectionData) + { + IPEndPoint ep; + try + { + ep = NetUtils.MakeEndPoint(address, port); + } + catch + { + CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost); + return null; + } + return Connect(ep, connectionData); + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Connection key + /// New NetPeer if new connection, Old NetPeer if already connected + /// Manager is not running. Call + public NetPeer Connect(IPEndPoint target, string key) + { + return Connect(target, NetDataWriter.FromString(key)); + } + + /// + /// Connect to remote host + /// + /// Server end point (ip and port) + /// Additional data for remote peer + /// New NetPeer if new connection, Old NetPeer if already connected + /// Manager is not running. Call + public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) + { + if (!IsRunning) + throw new InvalidOperationException("Client is not running"); + + NetPeer peer; + byte connectionNumber = 0; + if (TryGetPeer(target, out peer)) + { + switch (peer.ConnectionState) + { + //just return already connected peer + case ConnectionState.Connected: + case ConnectionState.Outcoming: + case ConnectionState.Incoming: + return peer; + } + //else reconnect + connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); + RemovePeer(peer); + } + //Create reliable connection + //And send connection request + return TryAddPeer(new NetPeer(this, target, GetNextPeerId(), connectionNumber, connectionData)); + } + + /// + /// Force closes connection and stop all threads. + /// + public void Stop() + { + Stop(true); + } + + /// + /// Force closes connection and stop all threads. + /// + /// Send disconnect messages + public void Stop(bool sendDisconnectMessages) + { + if (!IsRunning) + return; + NetDebug.Write("[NM] Stop"); + + //Send last disconnect + for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); + + //For working send + IsRunning = false; + + //Stop + _logicThread.Join(); + _logicThread = null; + _socket.Close(); + + //clear peers + _peersLock.EnterWriteLock(); + _headPeer = null; + _peersDict.Clear(); + _peersLock.ExitWriteLock(); +#if DEBUG + lock (_pingSimulationList) + _pingSimulationList.Clear(); +#endif + _connectedPeersCount = 0; + lock(_netEventsQueue) + _netEventsQueue.Clear(); + } + + /// + /// Return peers count with connection state + /// + /// peer connection state (you can use as bit flags) + /// peers count + public int GetPeersCount(ConnectionState peerState) + { + int count = 0; + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + count++; + } + return count; + } + + /// + /// Get copy of current connected peers (slow! use GetPeersNonAlloc for best performance) + /// + /// Array with connected peers + [Obsolete("Use GetPeers(ConnectionState peerState)")] + public NetPeer[] GetPeers() + { + return GetPeers(ConnectionState.Connected | ConnectionState.Outcoming); + } + + /// + /// Get copy of current connected peers (slow! use GetPeersNonAlloc for best performance) + /// + /// Array with connected peers + public NetPeer[] GetPeers(ConnectionState peerState) + { + List peersList = new List(); + GetPeersNonAlloc(peersList, peerState); + return peersList.ToArray(); + } + + /// + /// Get copy of peers (without allocations) + /// + /// List that will contain result + /// State of peers + public void GetPeersNonAlloc(List peers, ConnectionState peerState) + { + peers.Clear(); + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + if ((netPeer.ConnectionState & peerState) != 0) + peers.Add(netPeer); + } + } + + /// + /// Disconnect all peers without any additional data + /// + public void DisconnectAll() + { + DisconnectAll(null, 0, 0); + } + + /// + /// Disconnect all peers with shutdown message + /// + /// Data to send (must be less or equal MTU) + /// Data start + /// Data count + public void DisconnectAll(byte[] data, int start, int count) + { + //Send disconnect packets + for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) + { + DisconnectPeer( + netPeer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + start, + count, + null); + } + } + + /// + /// Immediately disconnect peer from server without additional data + /// + /// peer to disconnect + public void DisconnectPeerForce(NetPeer peer) + { + DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null); + } + + /// + /// Disconnect peer from server + /// + /// peer to disconnect + public void DisconnectPeer(NetPeer peer) + { + DisconnectPeer(peer, null, 0, 0); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(NetPeer peer, byte[] data) + { + DisconnectPeer(peer, data, 0, data.Length); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + public void DisconnectPeer(NetPeer peer, NetDataWriter writer) + { + DisconnectPeer(peer, writer.Data, 0, writer.Length); + } + + /// + /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) + /// + /// peer to disconnect + /// additional data + /// data start + /// data length + public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count) + { + DisconnectPeer( + peer, + DisconnectReason.DisconnectPeerCalled, + 0, + false, + data, + start, + count, + null); + } + + public NetPeerEnumerator GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new NetPeerEnumerator(_headPeer); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacket.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacket.cs new file mode 100644 index 0000000..4d743af --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacket.cs @@ -0,0 +1,256 @@ +using System; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + internal enum PacketProperty : byte + { + Unreliable, + Channeled, + Ack, + Ping, + Pong, + ConnectRequest, + ConnectAccept, + Disconnect, + UnconnectedMessage, + NatIntroductionRequest, + NatIntroduction, + NatPunchMessage, + MtuCheck, + MtuOk, + Broadcast, + Merged, + ShutdownOk, + PeerNotFound, + InvalidProtocol + } + + internal sealed class NetPacket + { + private static readonly int LastProperty = Enum.GetValues(typeof(PacketProperty)).Length; + //Header + public PacketProperty Property + { + get { return (PacketProperty)(RawData[0] & 0x1F); } + set { RawData[0] = (byte)((RawData[0] & 0xE0) | (byte)value); } + } + + public byte ConnectionNumber + { + get { return (byte)((RawData[0] & 0x60) >> 5); } + set { RawData[0] = (byte) ((RawData[0] & 0x9F) | (value << 5)); } + } + + public ushort Sequence + { + get { return BitConverter.ToUInt16(RawData, 1); } + set { FastBitConverter.GetBytes(RawData, 1, value); } + } + + public bool IsFragmented + { + get { return (RawData[0] & 0x80) != 0; } + } + + public void MarkFragmented() + { + RawData[0] |= 0x80; //set first bit + } + + public byte ChannelId + { + get { return RawData[3]; } + set { RawData[3] = value; } + } + + public ushort FragmentId + { + get { return BitConverter.ToUInt16(RawData, 4); } + set { FastBitConverter.GetBytes(RawData, 4, value); } + } + + public ushort FragmentPart + { + get { return BitConverter.ToUInt16(RawData, 6); } + set { FastBitConverter.GetBytes(RawData, 6, value); } + } + + public ushort FragmentsTotal + { + get { return BitConverter.ToUInt16(RawData, 8); } + set { FastBitConverter.GetBytes(RawData, 8, value); } + } + + //Data + public byte[] RawData; + public ushort Size; + + //Delivery + public object UserData; + + public NetPacket(int size) + { + RawData = new byte[size]; + Size = (ushort)size; + } + + public NetPacket(PacketProperty property, int size) + { + size += GetHeaderSize(property); + RawData = new byte[size]; + Property = property; + Size = (ushort)size; + } + + public void Realloc(int toSize, bool clear) + { + //ͷԴֻclear0 + Size = (ushort)toSize; + if (RawData.Length < toSize) + { + RawData = new byte[toSize]; + return; + } + if (clear) //clear not reallocated + Array.Clear(RawData, 0, toSize); + } + + public static int GetHeaderSize(PacketProperty property) + { + switch (property) + { + case PacketProperty.Channeled: + case PacketProperty.Ack: + return NetConstants.ChanneledHeaderSize; + case PacketProperty.Ping: + return NetConstants.HeaderSize + 2; + case PacketProperty.ConnectRequest: + return NetConnectRequestPacket.HeaderSize; + case PacketProperty.ConnectAccept: + return NetConnectAcceptPacket.Size; + case PacketProperty.Disconnect: + return NetConstants.HeaderSize + 8; + case PacketProperty.Pong: + return NetConstants.HeaderSize + 10; + default: + return NetConstants.HeaderSize; + } + } + + public int GetHeaderSize() + { + return GetHeaderSize(Property); + } + + //Packet contstructor from byte array + public bool FromBytes(byte[] data, int start, int packetSize) + { + //Reading property + byte property = (byte)(data[start] & 0x1F); + bool fragmented = (data[start] & 0x80) != 0; + int headerSize = GetHeaderSize((PacketProperty) property); + + if (property > LastProperty || packetSize < headerSize || + (fragmented && packetSize < headerSize + NetConstants.FragmentHeaderSize) || + data.Length < start + packetSize) + { + return false; + } + + Buffer.BlockCopy(data, start, RawData, 0, packetSize); + Size = (ushort)packetSize; + return true; + } + } + + internal sealed class NetConnectRequestPacket + { + public const int HeaderSize = 13; + public readonly long ConnectionTime; + public readonly byte ConnectionNumber; + public readonly NetDataReader Data; + + private NetConnectRequestPacket(long connectionId, byte connectionNumber, NetDataReader data) + { + ConnectionTime = connectionId; + ConnectionNumber = connectionNumber; + Data = data; + } + + public static int GetProtocolId(NetPacket packet) + { + return BitConverter.ToInt32(packet.RawData, 1); + } + + public static NetConnectRequestPacket FromData(NetPacket packet) + { + if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber) + return null; + + //Getting new id for peer + long connectionId = BitConverter.ToInt64(packet.RawData, 5); + + // Read data and create request + var reader = new NetDataReader(null, 0, 0); + if (packet.Size > HeaderSize) + reader.SetSource(packet.RawData, HeaderSize, packet.Size); + + return new NetConnectRequestPacket(connectionId, packet.ConnectionNumber, reader); + } + + public static NetPacket Make(NetDataWriter connectData, long connectId) + { + //Make initial packet + var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length); + + //Add data + FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId); + FastBitConverter.GetBytes(packet.RawData, 5, connectId); + Buffer.BlockCopy(connectData.Data, 0, packet.RawData, HeaderSize, connectData.Length); + return packet; + } + } + + internal sealed class NetConnectAcceptPacket + { + public const int Size = 11; + public readonly long ConnectionId; + public readonly byte ConnectionNumber; + public readonly bool IsReusedPeer; + + private NetConnectAcceptPacket(long connectionId, byte connectionNumber, bool isReusedPeer) + { + ConnectionId = connectionId; + ConnectionNumber = connectionNumber; + IsReusedPeer = isReusedPeer; + } + + public static NetConnectAcceptPacket FromData(NetPacket packet) + { + if (packet.Size > Size) + return null; + + long connectionId = BitConverter.ToInt64(packet.RawData, 1); + //check connect num + byte connectionNumber = packet.RawData[9]; + if (connectionNumber >= NetConstants.MaxConnectionNumber) + return null; + //check reused flag + byte isReused = packet.RawData[10]; + if (isReused > 1) + return null; + + return new NetConnectAcceptPacket(connectionId, connectionNumber, isReused == 1); + } + + public static NetPacket Make(long connectId, byte connectNum, bool reusedPeer) + { + var packet = new NetPacket(PacketProperty.ConnectAccept, 0); + FastBitConverter.GetBytes(packet.RawData, 1, connectId); + packet.RawData[9] = connectNum; + packet.RawData[10] = (byte)(reusedPeer ? 1 : 0); + return packet; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacketPool.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacketPool.cs new file mode 100644 index 0000000..9351eff --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPacketPool.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading; + +namespace LiteNetLib +{ + internal sealed class NetPacketPool + { + private readonly NetPacket[] _pool = new NetPacket[NetConstants.PacketPoolSize]; + + private int _count; + + public NetPacket GetWithData(PacketProperty property, byte[] data, int start, int length) + { + var packet = GetWithProperty(property, length); + Buffer.BlockCopy(data, start, packet.RawData, NetPacket.GetHeaderSize(property), length); + return packet; + } + + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + //这种精度的锁技术 有待研究 + public NetPacket GetPacket(int size, bool clear) + { + NetPacket packet = null; + if (size <= NetConstants.MaxPacketSize) + { + _lock.EnterUpgradeableReadLock(); + if (_count > 0) + { + _lock.EnterWriteLock(); + _count--; + packet = _pool[_count]; + _pool[_count] = null; + _lock.ExitWriteLock(); + } + _lock.ExitUpgradeableReadLock(); + } + if (packet == null) + { + //allocate new packet + packet = new NetPacket(size); + } + else + { + //reallocate packet data if packet not fits + packet.Realloc(size, clear); + } + return packet; + } + + //Get packet with size + public NetPacket GetWithProperty(PacketProperty property, int size) + { + size += NetPacket.GetHeaderSize(property); + NetPacket packet = GetPacket(size, true); + packet.Property = property; + return packet; + } + + public void Recycle(NetPacket packet) + { + if (packet.RawData.Length > NetConstants.MaxPacketSize) + { + //Dont pool big packets. Save memory + return; + } + + //Clean fragmented flag + packet.RawData[0] = 0; + + _lock.EnterUpgradeableReadLock(); + if (_count == NetConstants.PacketPoolSize) + { + _lock.ExitUpgradeableReadLock(); + return; + } + _lock.EnterWriteLock(); + _pool[_count] = packet; + _count++; + _lock.ExitWriteLock(); + _lock.ExitUpgradeableReadLock(); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetPeer.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPeer.cs new file mode 100644 index 0000000..6c6d604 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetPeer.cs @@ -0,0 +1,1108 @@ +#if DEBUG +#define STATS_ENABLED +#endif +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using LiteNetLib.Utils; + +namespace LiteNetLib +{ + /// + /// Peer connection state + /// + [Flags] + public enum ConnectionState : byte + { + Incoming = 1 << 1, + Outcoming = 1 << 2, + Connected = 1 << 3, + ShutdownRequested = 1 << 4, + Disconnected = 1 << 5, + Any = Incoming | Outcoming | Connected | ShutdownRequested + } + + internal enum ConnectRequestResult + { + None, + P2PConnection, //when peer connecting + Reconnection, //when peer was connected + NewConnection //when peer was disconnected + } + + internal enum DisconnectResult + { + None, + Reject, + Disconnect + } + + /// + /// Network peer. Main purpose is sending messages to specific peer. + /// + public class NetPeer + { + //Ping and RTT + private int _rtt; + private int _avgRtt; + private int _rttCount; + private double _resendDelay = 27.0; + private int _pingSendTimer; + private int _rttResetTimer; + private readonly Stopwatch _pingTimer = new Stopwatch(); + private int _timeSinceLastPacket; + private long _remoteDelta; + + //Common + private readonly NetPacketPool _packetPool; + private readonly object _flushLock = new object(); + private readonly object _sendLock = new object(); + private readonly object _shutdownLock = new object(); + + internal volatile NetPeer NextPeer; + internal NetPeer PrevPeer; + + internal byte ConnectionNum + { + get { return _connectNum; } + private set + { + _connectNum = value; + _mergeData.ConnectionNumber = value; + _pingPacket.ConnectionNumber = value; + _pongPacket.ConnectionNumber = value; + } + } + + //Channels + private readonly SimpleChannel _unreliableChannel; + private readonly BaseChannel[] _channels; + private BaseChannel _headChannel; + + //MTU + private int _mtu = NetConstants.PossibleMtu[0];//Ĭ572Сmtu + private int _mtuIdx; + private bool _finishMtu; + private int _mtuCheckTimer; + private int _mtuCheckAttempts; + private const int MtuCheckDelay = 1000; + private const int MaxMtuCheckAttempts = 4; + private readonly object _mtuMutex = new object(); + + //Fragment + private class IncomingFragments + { + public NetPacket[] Fragments; + public int ReceivedCount; + public int TotalSize; + public byte ChannelId; + } + private ushort _fragmentId; + private readonly Dictionary _holdedFragments; + private readonly Dictionary _deliveredFramgnets; + + //Merging + private readonly NetPacket _mergeData; + private int _mergePos; + private int _mergeCount; + + //Connection + private int _connectAttempts; + private int _connectTimer; + private long _connectTime; + private byte _connectNum; + private ConnectionState _connectionState; + private NetPacket _shutdownPacket; + private const int ShutdownDelay = 300; + private int _shutdownTimer; + private readonly NetPacket _pingPacket; + private readonly NetPacket _pongPacket; + private readonly NetPacket _connectRequestPacket; + private NetPacket _connectAcceptPacket; + + /// + /// Peer ip address and port + /// + public readonly IPEndPoint EndPoint; + + /// + /// Peer parent NetManager + /// + public readonly NetManager NetManager; + + /// + /// Current connection state + /// + public ConnectionState ConnectionState { get { return _connectionState; } } + + /// + /// Connection time for internal purposes + /// + internal long ConnectTime { get { return _connectTime; } } + + /// + /// Peer id can be used as key in your dictionary of peers + /// + public readonly int Id; + + /// + /// Current ping in milliseconds + /// + public int Ping { get { return _avgRtt/2; } } + + /// + /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation ) + /// + public int Mtu { get { return _mtu; } } + + /// + /// Delta with remote time in ticks (not accurate) + /// positive - remote time > our time + /// + public long RemoteTimeDelta + { + get { return _remoteDelta; } + } + + /// + /// Remote UTC time (not accurate) + /// + public DateTime RemoteUtcTime + { + get { return new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); } + } + + /// + /// Time since last packet received (including internal library packets) + /// + public int TimeSinceLastPacket { get { return _timeSinceLastPacket; } } + + internal double ResendDelay { get { return _resendDelay; } } + + /// + /// Application defined object containing data about the connection + /// + public object Tag; + + /// + /// Statistics of peer connection + /// + public readonly NetStatistics Statistics; + + //incoming connection constructor + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) + { + Id = id; + Statistics = new NetStatistics(); + _packetPool = netManager.NetPacketPool; + NetManager = netManager; + EndPoint = remoteEndPoint; + _connectionState = ConnectionState.Incoming; + _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize); + _pongPacket = new NetPacket(PacketProperty.Pong, 0); + _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1}; + + _unreliableChannel = new SimpleChannel(this); + _headChannel = _unreliableChannel; + _holdedFragments = new Dictionary(); + _deliveredFramgnets = new Dictionary(); + + _channels = new BaseChannel[netManager.ChannelsCount * 4]; + } + + private BaseChannel CreateChannel(byte idx) + { + BaseChannel newChannel = _channels[idx]; + if (newChannel != null) + return newChannel; + switch ((DeliveryMethod)(idx % 4)) + { + case DeliveryMethod.ReliableUnordered: + newChannel = new ReliableChannel(this, false, idx); + break; + case DeliveryMethod.Sequenced: + newChannel = new SequencedChannel(this, false, idx); + break; + case DeliveryMethod.ReliableOrdered: + newChannel = new ReliableChannel(this, true, idx); + break; + case DeliveryMethod.ReliableSequenced: + newChannel = new SequencedChannel(this, true, idx); + break; + } + _channels[idx] = newChannel; + newChannel.Next = _headChannel; + _headChannel = newChannel; + return newChannel; + } + + //"Connect to" constructor + internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData) + : this(netManager, remoteEndPoint, id) + { + _connectTime = DateTime.UtcNow.Ticks; + _connectionState = ConnectionState.Outcoming; + ConnectionNum = connectNum; + + //Make initial packet + _connectRequestPacket = NetConnectRequestPacket.Make(connectData, _connectTime); + _connectRequestPacket.ConnectionNumber = connectNum; + + //Send request + NetManager.SendRaw(_connectRequestPacket, EndPoint); + + NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}, ConnectNum: {1}", _connectTime, connectNum); + } + + //"Accept" incoming constructor + internal void Accept(long connectId, byte connectNum) + { + _connectTime = connectId; + _connectionState = ConnectionState.Connected; + ConnectionNum = connectNum; + + //Make initial packet + _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, connectNum, false); + //Send + NetManager.SendRaw(_connectAcceptPacket, EndPoint); + + NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}", _connectTime); + } + + internal bool ProcessConnectAccept(NetConnectAcceptPacket packet) + { + if (_connectionState != ConnectionState.Outcoming) + return false; + + //check connection id + if (packet.ConnectionId != _connectTime) + { + NetDebug.Write(NetLogLevel.Trace, "[NC] Invalid connectId: {0}", _connectTime); + return false; + } + //check connect num + ConnectionNum = packet.ConnectionNumber; + + NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept"); + _timeSinceLastPacket = 0; + _connectionState = ConnectionState.Connected; + return true; + } + + /// + /// Gets maximum size of packet that will be not fragmented. + /// + /// Type of packet that you want send + /// size in bytes + public int GetMaxSinglePacketSize(DeliveryMethod options) + { + return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(data, start, length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer with delivery event called + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// User data that will be received in DeliveryEvent + /// + /// If you trying to send unreliable packet type + /// + public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod, object userData) + { + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets"); + SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, userData); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, DeliveryMethod deliveryMethod) + { + SendInternal(data, 0, data.Length, 0, deliveryMethod, null); + } + + /// + /// Send data to peer (channel - 0) + /// + /// DataWriter with data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod) + { + SendInternal(dataWriter.Data, 0, dataWriter.Length, 0, deliveryMethod, null); + } + + /// + /// Send data to peer (channel - 0) + /// + /// Data + /// Start of data + /// Length of data + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, int start, int length, DeliveryMethod options) + { + SendInternal(data, start, length, 0, options, null); + } + + /// + /// Send data to peer + /// + /// Data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, null); + } + + /// + /// Send data to peer + /// + /// DataWriter with data + /// Number of channel (from 0 to channelsCount - 1) + /// Send options (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, null); + } + + /// + /// peer + /// + /// Data + /// Start of data + /// Length of data + /// Number of channel (from 0 to channelsCount - 1) + /// Delivery method (reliable, unreliable, etc.) + /// + /// If size exceeds maximum limit: + /// MTU - headerSize bytes for Unreliable + /// Fragment count exceeded ushort.MaxValue + /// + public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) + { + SendInternal(data, start, length, channelNumber, deliveryMethod, null); + } + + private void SendInternal( + byte[] data, + int start, + int length, + byte channelNumber, + DeliveryMethod deliveryMethod, + object userData) + { + if (_connectionState == ConnectionState.ShutdownRequested || + _connectionState == ConnectionState.Disconnected) + return; + if (channelNumber >= _channels.Length) + return; + + //Select channel + PacketProperty property; + BaseChannel channel; + + if (deliveryMethod == DeliveryMethod.Unreliable) + { + property = PacketProperty.Unreliable; + channel = _unreliableChannel; + } + else + { + property = PacketProperty.Channeled; + channel = CreateChannel((byte)(channelNumber*4 + (byte)deliveryMethod)); + } + + //Prepare + NetDebug.Write("[RS]Packet: " + property); + + //Check fragmentation + int headerSize = NetPacket.GetHeaderSize(property); + //Save mtu for multithread + int mtu = _mtu; + if (length + headerSize > mtu) + { + //if cannot be fragmented + //ܷƬ/ȷϲҰȴmtuֵ򱨴 + if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered) + throw new TooBigPacketException("Unreliable packet size exceeded maximum of " + (_mtu - headerSize) + " bytes"); + + int packetFullSize = mtu - headerSize; + int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize; + int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1); + + NetDebug.Write("FragmentSend:\n" + + " MTU: {0}\n" + + " headerSize: {1}\n" + + " packetFullSize: {2}\n" + + " packetDataSize: {3}\n" + + " totalPackets: {4}", + mtu, headerSize, packetFullSize, packetDataSize, totalPackets); + + if (totalPackets > ushort.MaxValue) + throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue); + + lock (_sendLock) + { + for(ushort partIdx = 0; partIdx < totalPackets; partIdx++) + { + int sendLength = length > packetDataSize ? packetDataSize : length; + + NetPacket p = _packetPool.GetWithProperty(property, sendLength + NetConstants.FragmentHeaderSize); + p.UserData = userData; + p.FragmentId = _fragmentId; + p.FragmentPart = partIdx; + p.FragmentsTotal = (ushort)totalPackets; + p.MarkFragmented(); + + + Buffer.BlockCopy(data, partIdx * packetDataSize, p.RawData, NetConstants.FragmentTotalSize, sendLength); + channel.AddToQueue(p); + + length -= sendLength; + } + _fragmentId++; + } + return; + } + + //Else just send + NetPacket packet = _packetPool.GetWithData(property, data, start, length); + packet.UserData = userData; + channel.AddToQueue(packet); + } + + public void Disconnect(byte[] data) + { + NetManager.DisconnectPeer(this, data); + } + + public void Disconnect(NetDataWriter writer) + { + NetManager.DisconnectPeer(this, writer); + } + + public void Disconnect(byte[] data, int start, int count) + { + NetManager.DisconnectPeer(this, data, start, count); + } + + public void Disconnect() + { + NetManager.DisconnectPeer(this); + } + + internal DisconnectResult ProcessDisconnect(NetPacket packet) + { + if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outcoming) && + packet.Size >= 9 && + BitConverter.ToInt64(packet.RawData, 1) == _connectTime && + packet.ConnectionNumber == _connectNum) + { + return _connectionState == ConnectionState.Connected + ? DisconnectResult.Disconnect + : DisconnectResult.Reject; + } + return DisconnectResult.None; + } + + internal void Reject(long connectionId, byte connectionNumber, byte[] data, int start, int length) + { + _connectTime = connectionId; + _connectNum = connectionNumber; + Shutdown(data, start, length, false); + } + + internal bool Shutdown(byte[] data, int start, int length, bool force) + { + lock (_shutdownLock) + { + //trying to shutdown already disconnected + if (_connectionState == ConnectionState.Disconnected || + _connectionState == ConnectionState.ShutdownRequested) + { + return false; + } + + //don't send anything + if (force) + { + _connectionState = ConnectionState.Disconnected; + return true; + } + + //reset time for reconnect protection + _timeSinceLastPacket = 0; + + //send shutdown packet + _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length); + _shutdownPacket.ConnectionNumber = _connectNum; + FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime); + if (_shutdownPacket.Size >= _mtu) + { + //Drop additional data + NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!"); + } + else if (data != null && length > 0) + { + Buffer.BlockCopy(data, start, _shutdownPacket.RawData, 9, length); + } + _connectionState = ConnectionState.ShutdownRequested; + NetDebug.Write("[Peer] Send disconnect"); + NetManager.SendRaw(_shutdownPacket, EndPoint); + return true; + } + } + + private void UpdateRoundTripTime(int roundTripTime) + { + _rtt += roundTripTime; + _rttCount++; + _avgRtt = _rtt/_rttCount; + _resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt + } + + internal void AddIncomingPacket(NetPacket p) + { + if (p.IsFragmented) + { + NetDebug.Write("Fragment. Id: {0}, Part: {1}, Total: {2}", p.FragmentId, p.FragmentPart, p.FragmentsTotal); + //Get needed array from dictionary + ushort packetFragId = p.FragmentId; + IncomingFragments incomingFragments; + if (!_holdedFragments.TryGetValue(packetFragId, out incomingFragments)) + { + incomingFragments = new IncomingFragments + { + Fragments = new NetPacket[p.FragmentsTotal], + ChannelId = p.ChannelId + }; + _holdedFragments.Add(packetFragId, incomingFragments); + } + + //Cache + var fragments = incomingFragments.Fragments; + + //Error check + if (p.FragmentPart >= fragments.Length || + fragments[p.FragmentPart] != null || + p.ChannelId != incomingFragments.ChannelId) + { + _packetPool.Recycle(p); + NetDebug.WriteError("Invalid fragment packet"); + return; + } + //Fill array + fragments[p.FragmentPart] = p; + + //Increase received fragments count + incomingFragments.ReceivedCount++; + + //Increase total size + incomingFragments.TotalSize += p.Size - NetConstants.FragmentTotalSize; + + //Check for finish + if (incomingFragments.ReceivedCount != fragments.Length) + return; + + NetPacket resultingPacket = _packetPool.GetWithProperty( p.Property, incomingFragments.TotalSize ); + resultingPacket.ChannelId = incomingFragments.ChannelId; + + int resultingPacketOffset = resultingPacket.GetHeaderSize(); + int firstFragmentSize = fragments[0].Size - NetConstants.FragmentTotalSize; + for (int i = 0; i < incomingFragments.ReceivedCount; i++) + { + //Create resulting big packet + int fragmentSize = fragments[i].Size - NetConstants.FragmentTotalSize; + Buffer.BlockCopy( + fragments[i].RawData, + NetConstants.FragmentTotalSize, + resultingPacket.RawData, + resultingPacketOffset + firstFragmentSize * i, + fragmentSize); + + //Free memory + _packetPool.Recycle(fragments[i]); + fragments[i] = null; + } + + //Send to process + NetManager.ReceiveFromPeer(resultingPacket, this); + + //Clear memory + _holdedFragments.Remove(packetFragId); + } + else //Just simple packet + { + NetManager.ReceiveFromPeer(p, this); + } + } + + private void ProcessMtuPacket(NetPacket packet) + { + //header + int + if (packet.Size < NetConstants.PossibleMtu[0]) + return; + + //first stage check (mtu check and mtu ok) + int receivedMtu = BitConverter.ToInt32(packet.RawData, 1); + int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4); + if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize) + { + NetDebug.WriteError("[MTU] Broken packet. RMTU {0}, EMTU {1}, PSIZE {2}", receivedMtu, endMtuCheck, packet.Size); + return; + } + + if (packet.Property == PacketProperty.MtuCheck) + { + _mtuCheckAttempts = 0; + NetDebug.Write("[MTU] check. send back: " + receivedMtu); + packet.Property = PacketProperty.MtuOk; + NetManager.SendRawAndRecycle(packet, EndPoint); + } + else if(receivedMtu > _mtu && !_finishMtu) //MtuOk + { + //invalid packet + if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1]) + return; + + lock (_mtuMutex) + { + _mtuIdx++; + _mtu = receivedMtu; + } + //if maxed - finish. + if (_mtuIdx == NetConstants.PossibleMtu.Length - 1) + _finishMtu = true; + + NetDebug.Write("[MTU] ok. Increase to: " + _mtu); + } + } + + private void UpdateMtuLogic(int deltaTime) + { + if (_finishMtu) + return; + + _mtuCheckTimer += deltaTime; + if (_mtuCheckTimer < MtuCheckDelay) + return; + + _mtuCheckTimer = 0; + _mtuCheckAttempts++; + if (_mtuCheckAttempts >= MaxMtuCheckAttempts) + { + _finishMtu = true; + return; + } + + lock (_mtuMutex) + { + if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1) + return; + + //Send increased packet + int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1]; + var p = _packetPool.GetWithProperty(PacketProperty.MtuCheck, newMtu - NetConstants.HeaderSize); + FastBitConverter.GetBytes(p.RawData, 1, newMtu); //place into start + FastBitConverter.GetBytes(p.RawData, p.Size - 4, newMtu);//and end of packet + + //Must check result for MTU fix + if (NetManager.SendRawAndRecycle(p, EndPoint) <= 0) + _finishMtu = true; + } + } + + internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest) + { + //current or new request + switch (_connectionState) + { + //P2P case or just ID update + case ConnectionState.Outcoming: + case ConnectionState.Incoming: + //change connect id if newer + if (connRequest.ConnectionTime >= _connectTime) + { + //Change connect id + _connectTime = connRequest.ConnectionTime; + ConnectionNum = connRequest.ConnectionNumber; + } + return _connectionState == ConnectionState.Outcoming + ? ConnectRequestResult.P2PConnection + : ConnectRequestResult.None; + + case ConnectionState.Connected: + //Old connect request + if (connRequest.ConnectionTime == _connectTime) + { + //just reply accept + NetManager.SendRaw(_connectAcceptPacket, EndPoint); + } + //New connect request + else if (connRequest.ConnectionTime > _connectTime) + { + return ConnectRequestResult.Reconnection; + } + break; + + case ConnectionState.Disconnected: + case ConnectionState.ShutdownRequested: + if (connRequest.ConnectionTime >= _connectTime) + { + return ConnectRequestResult.NewConnection; + } + break; + } + return ConnectRequestResult.None; + } + + //Process incoming packet + internal void ProcessPacket(NetPacket packet) + { + //not initialized + if (_connectionState == ConnectionState.Incoming) + { + _packetPool.Recycle(packet); + return; + } + if (packet.ConnectionNumber != _connectNum && packet.Property != PacketProperty.ShutdownOk) //without connectionNum + { + NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet"); + _packetPool.Recycle(packet); + return; + } + _timeSinceLastPacket = 0; + + NetDebug.Write("[RR]PacketProperty: {0}", packet.Property); + switch (packet.Property) + { + case PacketProperty.Merged: + int pos = NetConstants.HeaderSize; + while (pos < packet.Size) + { + ushort size = BitConverter.ToUInt16(packet.RawData, pos); + pos += 2; + NetPacket mergedPacket = _packetPool.GetPacket(size, false); + if (!mergedPacket.FromBytes(packet.RawData, pos, size)) + { + _packetPool.Recycle(packet); + break; + } + pos += size; + ProcessPacket(mergedPacket); + } + break; + //If we get ping, send pong + case PacketProperty.Ping: + if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0) + { + NetDebug.Write("[PP]Ping receive, send pong"); + FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks); + _pongPacket.Sequence = packet.Sequence; + NetManager.SendRaw(_pongPacket, EndPoint); + } + _packetPool.Recycle(packet); + break; + + //If we get pong, calculate ping time and rtt + case PacketProperty.Pong: + if (packet.Sequence == _pingPacket.Sequence) + { + _pingTimer.Stop(); + int elapsedMs = (int)_pingTimer.ElapsedMilliseconds; + _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks; + UpdateRoundTripTime(elapsedMs); + NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2); + NetDebug.Write("[PP]Ping: {0} - {1} - {2}", packet.Sequence, elapsedMs, _remoteDelta); + } + _packetPool.Recycle(packet); + break; + + case PacketProperty.Ack: + case PacketProperty.Channeled: + if (packet.ChannelId > _channels.Length) + { + _packetPool.Recycle(packet); + break; + } + var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId)); + if (channel != null) + { + if (!channel.ProcessPacket(packet)) + _packetPool.Recycle(packet); + } + break; + + //Simple packet without acks + case PacketProperty.Unreliable: + AddIncomingPacket(packet); + return; + + case PacketProperty.MtuCheck: + case PacketProperty.MtuOk: + ProcessMtuPacket(packet); + break; + + case PacketProperty.ShutdownOk: + if(_connectionState == ConnectionState.ShutdownRequested) + _connectionState = ConnectionState.Disconnected; + _packetPool.Recycle(packet); + break; + + default: + NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property); + break; + } + } + + private void SendMerged() + { + if (_mergeCount == 0) + return; + int bytesSent; + if (_mergeCount > 1) + { + NetDebug.Write("[P]Send merged: " + _mergePos + ", count: " + _mergeCount); + bytesSent = NetManager.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, EndPoint); + } + else + { + //Send without length information and merging + bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, EndPoint); + } +#if STATS_ENABLED + Statistics.PacketsSent++; + Statistics.BytesSent += (ulong)bytesSent; +#endif + _mergePos = 0; + _mergeCount = 0; + } + + internal void SendUserData(NetPacket packet) + { + packet.ConnectionNumber = _connectNum; + int mergedPacketSize = NetConstants.HeaderSize + packet.Size + 2; + const int sizeTreshold = 20; + if (mergedPacketSize + sizeTreshold >= _mtu) + { + NetDebug.Write(NetLogLevel.Trace, "[P]SendingPacket: " + packet.Property); + int bytesSent = NetManager.SendRaw(packet, EndPoint); +#if STATS_ENABLED + Statistics.PacketsSent++; + Statistics.BytesSent += (ulong)bytesSent; +#endif + return; + } + if (_mergePos + mergedPacketSize > _mtu) + SendMerged(); + + FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size); + Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size); + _mergePos += packet.Size + 2; + _mergeCount++; + //DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount); + } + + /// + /// Flush all queued packets + /// + public void Flush() + { + if (_connectionState != ConnectionState.Connected) + return; + lock (_flushLock) + { + BaseChannel currentChannel = _headChannel; + while (currentChannel != null) + { + currentChannel.SendNextPackets(); + currentChannel = currentChannel.Next; + } + SendMerged(); + } + } + + internal void Update(int deltaTime) + { + _timeSinceLastPacket += deltaTime; + switch (_connectionState) + { + case ConnectionState.Connected: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + NetDebug.Write( + "[UPDATE] Disconnect by timeout: {0} > {1}", + _timeSinceLastPacket, + NetManager.DisconnectTimeout); + NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null); + return; + } + break; + + case ConnectionState.ShutdownRequested: + if (_timeSinceLastPacket > NetManager.DisconnectTimeout) + { + _connectionState = ConnectionState.Disconnected; + } + else + { + _shutdownTimer += deltaTime; + if (_shutdownTimer >= ShutdownDelay) + { + _shutdownTimer = 0; + NetManager.SendRaw(_shutdownPacket, EndPoint); + } + } + return; + + case ConnectionState.Outcoming: + _connectTimer += deltaTime; + if (_connectTimer > NetManager.ReconnectDelay) + { + _connectTimer = 0; + _connectAttempts++; + if (_connectAttempts > NetManager.MaxConnectAttempts) + { + NetManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, 0, null); + return; + } + + //else send connect again + NetManager.SendRaw(_connectRequestPacket, EndPoint); + } + return; + + case ConnectionState.Disconnected: + case ConnectionState.Incoming: + return; + } + + //Send ping + _pingSendTimer += deltaTime; + if (_pingSendTimer >= NetManager.PingInterval) + { + NetDebug.Write("[PP] Send ping..."); + //reset timer + _pingSendTimer = 0; + //send ping + _pingPacket.Sequence++; + //ping timeout + if (_pingTimer.IsRunning) + UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds); + _pingTimer.Reset(); + _pingTimer.Start(); + NetManager.SendRaw(_pingPacket, EndPoint); + } + + //RTT - round trip time + _rttResetTimer += deltaTime; + if (_rttResetTimer >= NetManager.PingInterval * 3) + { + _rttResetTimer = 0; + _rtt = _avgRtt; + _rttCount = 1; + } + + UpdateMtuLogic(deltaTime); + + //Pending send + Flush(); + } + + //For reliable channel + internal void RecycleAndDeliver(NetPacket packet) + { + if (packet.UserData != null) + { + if (packet.IsFragmented) + { + ushort fragCount; + _deliveredFramgnets.TryGetValue(packet.FragmentId, out fragCount); + fragCount++; + if (fragCount == packet.FragmentsTotal) + { + NetManager.MessageDelivered(this, packet.UserData); + _deliveredFramgnets.Remove(packet.FragmentId); + } + else + { + _deliveredFramgnets[packet.FragmentId] = fragCount; + } + } + else + { + NetManager.MessageDelivered(this, packet.UserData); + } + packet.UserData = null; + } + _packetPool.Recycle(packet); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetSocket.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetSocket.cs new file mode 100644 index 0000000..4f7075f --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetSocket.cs @@ -0,0 +1,343 @@ +#if UNITY_4 || UNITY_5 || UNITY_5_3_OR_NEWER +#define UNITY +#endif +#if NETSTANDARD2_0 || NETCOREAPP2_0 +using System.Runtime.InteropServices; +#endif + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace LiteNetLib +{ + internal interface INetSocketListener + { + void OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint); + } + + internal sealed class NetSocket + { + public const int ReceivePollingTime = 1000000; //1 second + private Socket _udpSocketv4; + private Socket _udpSocketv6; + private Thread _threadv4; + private Thread _threadv6; + private volatile bool _running; + private readonly INetSocketListener _listener; + private const int SioUdpConnreset = -1744830452; //SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 + private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("FF02:0:0:0:0:0:0:1"); + internal static readonly bool IPv6Support; + + public int LocalPort { get; private set; } + + public short Ttl + { + get { return _udpSocketv4.Ttl; } + set { _udpSocketv4.Ttl = value; } + } + + static NetSocket() + { +#if DISABLE_IPV6 || (!UNITY_EDITOR && ENABLE_IL2CPP && !UNITY_2018_3_OR_NEWER) + IPv6Support = false; +#elif !UNITY_2019_1_OR_NEWER && !UNITY_2018_4_OR_NEWER && (!UNITY_EDITOR && ENABLE_IL2CPP && UNITY_2018_3_OR_NEWER) + string version = UnityEngine.Application.unityVersion; + IPv6Support = Socket.OSSupportsIPv6 && int.Parse(version.Remove(version.IndexOf('f')).Split('.')[2]) >= 6; +#elif UNITY_2018_2_OR_NEWER + IPv6Support = Socket.OSSupportsIPv6; +#elif UNITY +#pragma warning disable 618 + IPv6Support = Socket.SupportsIPv6; +#pragma warning restore 618 +#else + IPv6Support = Socket.OSSupportsIPv6; //жǷ֧ipv6 +#endif + } + + public NetSocket(INetSocketListener listener) + { + _listener = listener; + } + + /// + /// receiveҪ߼ + /// + /// + private void ReceiveLogic(object state) + { + Socket socket = (Socket)state; + EndPoint bufferEndPoint = new IPEndPoint(socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0); + byte[] receiveBuffer = new byte[NetConstants.MaxPacketSize]; + + while (_running) + { + int result; + + //Reading data + try + { + if (socket.Available == 0 && !socket.Poll(ReceivePollingTime, SelectMode.SelectRead)) + continue; + result = socket.ReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, + ref bufferEndPoint); + } + catch (SocketException ex) + { + switch (ex.SocketErrorCode) + { + case SocketError.Interrupted: + case SocketError.NotSocket: + return; + case SocketError.ConnectionReset: + case SocketError.MessageSize: + case SocketError.TimedOut: + NetDebug.Write(NetLogLevel.Trace, "[R]Ignored error: {0} - {1}", + (int) ex.SocketErrorCode, ex.ToString()); + break; + default: + NetDebug.WriteError("[R]Error code: {0} - {1}", (int) ex.SocketErrorCode, + ex.ToString()); + _listener.OnMessageReceived(null, 0, ex.SocketErrorCode, (IPEndPoint) bufferEndPoint); + break; + } + + continue; + } + catch (ObjectDisposedException) + { + return; + } + + //All ok! + NetDebug.Write(NetLogLevel.Trace, "[R]Received data from {0}, result: {1}", bufferEndPoint.ToString(), result); + _listener.OnMessageReceived(receiveBuffer, result, 0, (IPEndPoint)bufferEndPoint); + } + } + + public bool Bind(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool reuseAddress, bool ipv6) + { + _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port), reuseAddress)) + return false; + LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port; + _running = true; + _threadv4 = new Thread(ReceiveLogic); + _threadv4.Name = "SocketThreadv4(" + LocalPort + ")"; + _threadv4.IsBackground = true; + _threadv4.Start(_udpSocketv4); + + //Check IPv6 support + if (!IPv6Support || !ipv6) + return true; + + _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + //Use one port for two sockets + if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort), reuseAddress)) + { + try + { +#if !UNITY + _udpSocketv6.SetSocketOption( + SocketOptionLevel.IPv6, + SocketOptionName.AddMembership, + new IPv6MulticastOption(MulticastAddressV6)); +#endif + } + catch(Exception) + { + // Unity3d throws exception - ignored + } + + _threadv6 = new Thread(ReceiveLogic); + _threadv6.Name = "SocketThreadv6(" + LocalPort + ")"; + _threadv6.IsBackground = true; + _threadv6.Start(_udpSocketv6); + } + + return true; + } + + private bool BindSocket(Socket socket, IPEndPoint ep, bool reuseAddress) + { + //Setup socket + socket.ReceiveTimeout = 500; + socket.SendTimeout = 500; + socket.ReceiveBufferSize = NetConstants.SocketBufferSize; + socket.SendBufferSize = NetConstants.SocketBufferSize; + try + { + //֪ʶ1 UDPͨŹУͻ;ϿյһSocketException + //IDΪ10054ǡԶǿȹرһеӡŵ¾ͿˣUDP + //ֹпͻ˶ܵӰ졣Ҳ˵һͻ쳣ϵͳı + //UDPӵģǵʵֳʹʱԶ쳣رգ׳һ쳣Զǿйرһ + //һ쳣׳ûмʱôUDP񶼽ܽκ + socket.IOControl(SioUdpConnreset, new byte[] {0}, null); + } + catch + { + //ignored + } + + try + { + socket.ExclusiveAddressUse = !reuseAddress; + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress); + } + catch + { + //Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it + } + if (socket.AddressFamily == AddressFamily.InterNetwork) + { + socket.Ttl = NetConstants.SocketTTL; + +#if NETSTANDARD2_0 || NETCOREAPP2_0 + if(!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) +#endif + try { socket.DontFragment = true; } + catch (SocketException e) + { + NetDebug.WriteError("[B]DontFragment error: {0}", e.SocketErrorCode); + } + + try { socket.EnableBroadcast = true; } + catch (SocketException e) + { + NetDebug.WriteError("[B]Broadcast error: {0}", e.SocketErrorCode); + } + } + + //Bind + try + { + socket.Bind(ep); + NetDebug.Write(NetLogLevel.Trace, "[B]Successfully binded to port: {0}", ((IPEndPoint)socket.LocalEndPoint).Port); + } + catch (SocketException bindException) + { + switch (bindException.SocketErrorCode) + { + //IPv6 bind fix + case SocketError.AddressAlreadyInUse: + if (socket.AddressFamily == AddressFamily.InterNetworkV6) + { + try + { + socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, true); + socket.Bind(ep); + } + catch (SocketException ex) + { + NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", ex.ToString(), ex.SocketErrorCode); + return false; + } + return true; + } + break; + //hack for iOS (Unity3D) + case SocketError.AddressFamilyNotSupported: + return true; + } + NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", bindException.ToString(), bindException.SocketErrorCode); + return false; + } + return true; + } + + public bool SendBroadcast(byte[] data, int offset, int size, int port) + { + bool broadcastSuccess = false; + bool multicastSuccess = false; + try + { + broadcastSuccess = _udpSocketv4.SendTo( + data, + offset, + size, + SocketFlags.None, + new IPEndPoint(IPAddress.Broadcast, port)) > 0; + + if (_udpSocketv6 != null) + { + multicastSuccess = _udpSocketv6.SendTo( + data, + offset, + size, + SocketFlags.None, + new IPEndPoint(MulticastAddressV6, port)) > 0; + } + } + catch (Exception ex) + { + NetDebug.WriteError("[S][MCAST]" + ex); + return broadcastSuccess; + } + return broadcastSuccess || multicastSuccess; + } + + public int SendTo(byte[] data, int offset, int size, IPEndPoint remoteEndPoint, ref SocketError errorCode) + { + try + { + var socket = _udpSocketv4; + if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support) + socket = _udpSocketv6; + int result = socket.SendTo(data, offset, size, SocketFlags.None, remoteEndPoint); + NetDebug.Write(NetLogLevel.Trace, "[S]Send packet to {0}, result: {1}", remoteEndPoint, result); + return result; + } + catch (SocketException ex) + { + switch (ex.SocketErrorCode) + { + case SocketError.NoBufferSpaceAvailable: + case SocketError.Interrupted: + return 0; + case SocketError.MessageSize: //do nothing + break; + default: + NetDebug.WriteError("[S]" + ex); + break; + } + errorCode = ex.SocketErrorCode; + return -1; + } + catch (Exception ex) + { + NetDebug.WriteError("[S]" + ex); + return -1; + } + } + + public void Close() + { + _running = false; + // first close sockets + if (_udpSocketv4 != null) + { + _udpSocketv4.Close(); + _udpSocketv4 = null; + } + if (_udpSocketv6 != null) + { + _udpSocketv6.Close(); + _udpSocketv6 = null; + } + // then join threads + if (_threadv4 != null) + { + if (_threadv4 != Thread.CurrentThread) + _threadv4.Join(); + _threadv4 = null; + } + if (_threadv6 != null) + { + if (_threadv6 != Thread.CurrentThread) + _threadv6.Join(); + _threadv6 = null; + } + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetStatistics.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetStatistics.cs new file mode 100644 index 0000000..e763e7a --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetStatistics.cs @@ -0,0 +1,39 @@ +namespace LiteNetLib +{ + public sealed class NetStatistics + { + public ulong PacketsSent; + public ulong PacketsReceived; + public ulong BytesSent; + public ulong BytesReceived; + public ulong PacketLoss; + public ulong PacketLossPercent + { + get { return PacketsSent == 0 ? 0 : PacketLoss * 100 / PacketsSent; } + } + + public ulong SequencedPacketLoss; + + public void Reset() + { + PacketsSent = 0; + PacketsReceived = 0; + BytesSent = 0; + BytesReceived = 0; + PacketLoss = 0; + } + + public override string ToString() + { + return + string.Format( + "BytesReceived: {0}\nPacketsReceived: {1}\nBytesSent: {2}\nPacketsSent: {3}\nPacketLoss: {4}\nPacketLossPercent: {5}\n", + BytesReceived, + PacketsReceived, + BytesSent, + PacketsSent, + PacketLoss, + PacketLossPercent); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/NetUtils.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/NetUtils.cs new file mode 100644 index 0000000..90329ea --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/NetUtils.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; + +namespace LiteNetLib +{ + /// + /// Address type that you want to receive from NetUtils.GetLocalIp method + /// + [Flags] + public enum LocalAddrType + { + IPv4 = 1, + IPv6 = 2, + All = IPv4 | IPv6 + } + + /// + /// 繫 + /// + public static class NetUtils + { + public static IPEndPoint MakeEndPoint(string hostStr, int port) + { + return new IPEndPoint(ResolveAddress(hostStr), port); + } + + public static IPAddress ResolveAddress(string hostStr) + { + if(hostStr == "localhost") + return IPAddress.Loopback; + + IPAddress ipAddress; + if (!IPAddress.TryParse(hostStr, out ipAddress)) + { + if (NetSocket.IPv6Support) + ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6); + if (ipAddress == null) + ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork); + } + if (ipAddress == null) + throw new ArgumentException("Invalid address: " + hostStr); + + return ipAddress; + } + + private static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily) + { + IPAddress[] addresses = ResolveAddresses(hostStr); + foreach (IPAddress ip in addresses) + { + if (ip.AddressFamily == addressFamily) + { + return ip; + } + } + return null; + } + + private static IPAddress[] ResolveAddresses(string hostStr) + { +#if NETSTANDARD2_0 || NETCOREAPP2_0 + var hostTask = Dns.GetHostEntryAsync(hostStr); + hostTask.GetAwaiter().GetResult(); + var host = hostTask.Result; +#else + var host = Dns.GetHostEntry(hostStr); +#endif + return host.AddressList; + } + + /// + /// Get all local ip addresses + /// + /// type of address (IPv4, IPv6 or both) + /// List with all local ip adresses + public static List GetLocalIpList(LocalAddrType addrType) + { + List targetList = new List(); + GetLocalIpList(targetList, addrType); + return targetList; + } + + /// + ///ȡеıip (non alloc version) + /// + /// result list + /// type of address (IPv4, IPv6 or both) + public static void GetLocalIpList(IList targetList, LocalAddrType addrType) + { + bool ipv4 = (addrType & LocalAddrType.IPv4) == LocalAddrType.IPv4; + bool ipv6 = (addrType & LocalAddrType.IPv6) == LocalAddrType.IPv6; + try + { + foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) + { + //Skip loopback and disabled network interfaces + if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || + ni.OperationalStatus != OperationalStatus.Up) + continue; + + var ipProps = ni.GetIPProperties(); + + //Skip address without gateway + if (ipProps.GatewayAddresses.Count == 0) + continue; + + foreach (UnicastIPAddressInformation ip in ipProps.UnicastAddresses) + { + var address = ip.Address; + if ((ipv4 && address.AddressFamily == AddressFamily.InterNetwork) || + (ipv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) + targetList.Add(address.ToString()); + } + } + } + catch + { + //ignored + } + + //Fallback mode (unity android) + if (targetList.Count == 0) + { + IPAddress[] addresses = ResolveAddresses(Dns.GetHostName()); + foreach (IPAddress ip in addresses) + { + if((ipv4 && ip.AddressFamily == AddressFamily.InterNetwork) || + (ipv6 && ip.AddressFamily == AddressFamily.InterNetworkV6)) + targetList.Add(ip.ToString()); + } + } + if (targetList.Count == 0) + { + if(ipv4) + targetList.Add("127.0.0.1"); + if(ipv6) + targetList.Add("::1"); + } + } + + private static readonly List IpList = new List(); + /// + /// Get first detected local ip address + /// + /// type of address (IPv4, IPv6 or both) + /// IP address if available. Else - string.Empty + public static string GetLocalIp(LocalAddrType addrType) + { + lock (IpList) + { + IpList.Clear(); + GetLocalIpList(IpList, addrType); + return IpList.Count == 0 ? string.Empty : IpList[0]; + } + } + + // =========================================== + // Internal and debug log related stuff + // =========================================== + public static void PrintInterfaceInfos() + { + NetDebug.WriteForce(NetLogLevel.Info, "IPv6Support: {0}", NetSocket.IPv6Support); + try + { + foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) + { + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + { + if (ip.Address.AddressFamily == AddressFamily.InterNetwork || + ip.Address.AddressFamily == AddressFamily.InterNetworkV6) + { + NetDebug.WriteForce( + NetLogLevel.Info, + "Interface: {0}, Type: {1}, Ip: {2}, OpStatus: {3}", + ni.Name, + ni.NetworkInterfaceType.ToString(), + ip.Address.ToString(), + ni.OperationalStatus.ToString()); + } + } + } + } + catch (Exception e) + { + NetDebug.WriteForce(NetLogLevel.Info, "Error while getting interface infos: {0}", e.ToString()); + } + } + + internal static int RelativeSequenceNumber(int number, int expected) + { + return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/ReliableChannel.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/ReliableChannel.cs new file mode 100644 index 0000000..0634e80 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/ReliableChannel.cs @@ -0,0 +1,310 @@ +using System; + +namespace LiteNetLib +{ + internal sealed class ReliableChannel : BaseChannel + { + private struct PendingPacket + { + private NetPacket _packet; + private long _timeStamp; + private bool _isSent; + + public override string ToString() + { + return _packet == null ? "Empty" : _packet.Sequence.ToString(); + } + + public void Init(NetPacket packet) + { + _packet = packet; + _isSent = false; + } + + public void TrySend(long currentTime, NetPeer peer) + { + if (_packet == null) + return; + if (_isSent) //check send time + { + double resendDelay = peer.ResendDelay * TimeSpan.TicksPerMillisecond; + double packetHoldTime = currentTime - _timeStamp; + if (packetHoldTime < resendDelay) + return; + NetDebug.Write("[RC]Resend: {0} > {1}", (int)packetHoldTime, resendDelay); + } + _timeStamp = currentTime; + _isSent = true; + peer.SendUserData(_packet); + } + + public bool Clear(NetPeer peer) + { + if (_packet != null) + { + peer.RecycleAndDeliver(_packet); + _packet = null; + return true; + } + return false; + } + } + + private readonly NetPacket _outgoingAcks; //for send acks + private readonly PendingPacket[] _pendingPackets; //for unacked packets and duplicates + private readonly NetPacket[] _receivedPackets; //for order + private readonly bool[] _earlyReceived; //for unordered + + private int _localSeqence; + private int _remoteSequence; + private int _localWindowStart; + private int _remoteWindowStart; + + private bool _mustSendAcks; + + private readonly bool _ordered; + private readonly int _windowSize; + private const int BitsInByte = 8; + private readonly byte _id; + + public ReliableChannel(NetPeer peer, bool ordered, byte id) : base(peer) + { + _id = id; + _windowSize = NetConstants.DefaultWindowSize; + _ordered = ordered; + _pendingPackets = new PendingPacket[_windowSize]; + for (int i = 0; i < _pendingPackets.Length; i++) + _pendingPackets[i] = new PendingPacket(); + + if (_ordered) + _receivedPackets = new NetPacket[_windowSize]; + else + _earlyReceived = new bool[_windowSize]; + + _localWindowStart = 0; + _localSeqence = 0; + _remoteSequence = 0; + _remoteWindowStart = 0; + _outgoingAcks = new NetPacket(PacketProperty.Ack, (_windowSize - 1) / BitsInByte + 2) {ChannelId = id}; + } + + //ProcessAck in packet + private void ProcessAck(NetPacket packet) + { + if (packet.Size != _outgoingAcks.Size) + { + NetDebug.Write("[PA]Invalid acks packet size"); + return; + } + + ushort ackWindowStart = packet.Sequence; + int windowRel = NetUtils.RelativeSequenceNumber(_localWindowStart, ackWindowStart); + if (ackWindowStart >= NetConstants.MaxSequence || windowRel < 0) + { + NetDebug.Write("[PA]Bad window start"); + return; + } + + //check relevance + if (windowRel >= _windowSize) + { + NetDebug.Write("[PA]Old acks"); + return; + } + + byte[] acksData = packet.RawData; + lock (_pendingPackets) + { + for (int pendingSeq = _localWindowStart; + pendingSeq != _localSeqence; + pendingSeq = (pendingSeq + 1) % NetConstants.MaxSequence) + { + int rel = NetUtils.RelativeSequenceNumber(pendingSeq, ackWindowStart); + if (rel >= _windowSize) + { + NetDebug.Write("[PA]REL: " + rel); + break; + } + + int pendingIdx = pendingSeq % _windowSize; + int currentByte = NetConstants.ChanneledHeaderSize + pendingIdx / BitsInByte; + int currentBit = pendingIdx % BitsInByte; + if ((acksData[currentByte] & (1 << currentBit)) == 0) + { +#if STATS_ENABLED || DEBUG + Peer.Statistics.PacketLoss++; +#endif + //Skip false ack + NetDebug.Write("[PA]False ack: {0}", pendingSeq); + continue; + } + + if (pendingSeq == _localWindowStart) + { + //Move window + _localWindowStart = (_localWindowStart + 1) % NetConstants.MaxSequence; + } + + //clear packet + if (_pendingPackets[pendingIdx].Clear(Peer)) + NetDebug.Write("[PA]Removing reliableInOrder ack: {0} - true", pendingSeq); + } + } + } + + public override void SendNextPackets() + { + if (_mustSendAcks) + { + _mustSendAcks = false; + NetDebug.Write("[RR]SendAcks"); + lock(_outgoingAcks) + Peer.SendUserData(_outgoingAcks); + } + + long currentTime = DateTime.UtcNow.Ticks; + lock (_pendingPackets) + { + //get packets from queue + lock (OutgoingQueue) + { + while (OutgoingQueue.Count > 0) + { + int relate = NetUtils.RelativeSequenceNumber(_localSeqence, _localWindowStart); + if (relate >= _windowSize) + break; + + var netPacket = OutgoingQueue.Dequeue(); + netPacket.Sequence = (ushort) _localSeqence; + netPacket.ChannelId = _id; + _pendingPackets[_localSeqence % _windowSize].Init(netPacket); + _localSeqence = (_localSeqence + 1) % NetConstants.MaxSequence; + } + } + //send + for (int pendingSeq = _localWindowStart; pendingSeq != _localSeqence; pendingSeq = (pendingSeq + 1) % NetConstants.MaxSequence) + _pendingPackets[pendingSeq % _windowSize].TrySend(currentTime, Peer); + } + } + + //Process incoming packet + public override bool ProcessPacket(NetPacket packet) + { + if (packet.Property == PacketProperty.Ack) + { + ProcessAck(packet); + return false; + } + int seq = packet.Sequence; + if (seq >= NetConstants.MaxSequence) + { + NetDebug.Write("[RR]Bad sequence"); + return false; + } + + int relate = NetUtils.RelativeSequenceNumber(seq, _remoteWindowStart); + int relateSeq = NetUtils.RelativeSequenceNumber(seq, _remoteSequence); + + if (relateSeq > _windowSize) + { + NetDebug.Write("[RR]Bad sequence"); + return false; + } + + //Drop bad packets + if (relate < 0) + { + //Too old packet doesn't ack + NetDebug.Write("[RR]ReliableInOrder too old"); + return false; + } + if (relate >= _windowSize * 2) + { + //Some very new packet + NetDebug.Write("[RR]ReliableInOrder too new"); + return false; + } + + //If very new - move window + int ackIdx; + int ackByte; + int ackBit; + lock (_outgoingAcks) + { + if (relate >= _windowSize) + { + //New window position + int newWindowStart = (_remoteWindowStart + relate - _windowSize + 1) % NetConstants.MaxSequence; + _outgoingAcks.Sequence = (ushort) newWindowStart; + + //Clean old data + while (_remoteWindowStart != newWindowStart) + { + ackIdx = _remoteWindowStart % _windowSize; + ackByte = NetConstants.ChanneledHeaderSize + ackIdx / BitsInByte; + ackBit = ackIdx % BitsInByte; + _outgoingAcks.RawData[ackByte] &= (byte) ~(1 << ackBit); + _remoteWindowStart = (_remoteWindowStart + 1) % NetConstants.MaxSequence; + } + } + + //Final stage - process valid packet + //trigger acks send + _mustSendAcks = true; + ackIdx = seq % _windowSize; + ackByte = NetConstants.ChanneledHeaderSize + ackIdx / BitsInByte; + ackBit = ackIdx % BitsInByte; + if ((_outgoingAcks.RawData[ackByte] & (1 << ackBit)) != 0) + { + NetDebug.Write("[RR]ReliableInOrder duplicate"); + return false; + } + + //save ack + _outgoingAcks.RawData[ackByte] |= (byte) (1 << ackBit); + } + + //detailed check + if (seq == _remoteSequence) + { + NetDebug.Write("[RR]ReliableInOrder packet succes"); + Peer.AddIncomingPacket(packet); + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + + if (_ordered) + { + NetPacket p; + while ((p = _receivedPackets[_remoteSequence % _windowSize]) != null) + { + //process holded packet + _receivedPackets[_remoteSequence % _windowSize] = null; + Peer.AddIncomingPacket(p); + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + } + } + else + { + while (_earlyReceived[_remoteSequence % _windowSize]) + { + //process early packet + _earlyReceived[_remoteSequence % _windowSize] = false; + _remoteSequence = (_remoteSequence + 1) % NetConstants.MaxSequence; + } + } + return true; + } + + //holded packet + if (_ordered) + { + _receivedPackets[ackIdx] = packet; + } + else + { + _earlyReceived[ackIdx] = true; + Peer.AddIncomingPacket(packet); + } + return true; + } + } +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/SequencedChannel.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/SequencedChannel.cs new file mode 100644 index 0000000..0cc6793 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/SequencedChannel.cs @@ -0,0 +1,78 @@ +namespace LiteNetLib +{ + internal sealed class SequencedChannel : BaseChannel + { + private int _localSequence; + private ushort _remoteSequence; + private readonly bool _reliable; + private NetPacket _lastPacket; + private readonly NetPacket _ackPacket; + private bool _mustSendAck; + private readonly byte _id; + + public SequencedChannel(NetPeer peer, bool reliable, byte id) : base(peer) + { + _id = id; + _reliable = reliable; + if (_reliable) + _ackPacket = new NetPacket(PacketProperty.Ack, 0) {ChannelId = id}; + } + + public override void SendNextPackets() + { + if (_reliable && OutgoingQueue.Count == 0) + { + var packet = _lastPacket; + if(packet != null) + Peer.SendUserData(packet); + } + else + { + lock (OutgoingQueue) + { + while (OutgoingQueue.Count > 0) + { + NetPacket packet = OutgoingQueue.Dequeue(); + _localSequence = (_localSequence + 1) % NetConstants.MaxSequence; + packet.Sequence = (ushort)_localSequence; + packet.ChannelId = _id; + Peer.SendUserData(packet); + + if (_reliable && OutgoingQueue.Count == 0) + _lastPacket = packet; + else + Peer.NetManager.NetPacketPool.Recycle(packet); + } + } + } + + if (_reliable && _mustSendAck) + { + _mustSendAck = false; + _ackPacket.Sequence = _remoteSequence; + Peer.SendUserData(_ackPacket); + } + } + + public override bool ProcessPacket(NetPacket packet) + { + if (packet.Property == PacketProperty.Ack) + { + if (_reliable && _lastPacket != null && packet.Sequence == _lastPacket.Sequence) + _lastPacket = null; + return false; + } + int relative = NetUtils.RelativeSequenceNumber(packet.Sequence, _remoteSequence); + bool packetProcessed = false; + if (packet.Sequence < NetConstants.MaxSequence && relative > 0) + { + Peer.Statistics.PacketLoss += (ulong)(relative - 1); + _remoteSequence = packet.Sequence; + Peer.AddIncomingPacket(packet); + packetProcessed = true; + } + _mustSendAck = true; + return packetProcessed; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/SimpleChannel.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/SimpleChannel.cs new file mode 100644 index 0000000..eb9c862 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/SimpleChannel.cs @@ -0,0 +1,28 @@ +namespace LiteNetLib +{ + internal sealed class SimpleChannel : BaseChannel + { + public SimpleChannel(NetPeer peer) : base(peer) + { + + } + + public override void SendNextPackets() + { + lock (OutgoingQueue) + { + while (OutgoingQueue.Count > 0) + { + NetPacket packet = OutgoingQueue.Dequeue(); + Peer.SendUserData(packet); + Peer.NetManager.NetPacketPool.Recycle(packet); + } + } + } + + public override bool ProcessPacket(NetPacket packet) + { + return false; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/CRC32C.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/CRC32C.cs new file mode 100644 index 0000000..dd677e7 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/CRC32C.cs @@ -0,0 +1,111 @@ +#if NETCOREAPP3_0 +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +#endif + +namespace LiteNetLib.Utils +{ + //Implementation from Crc32.NET + public static class CRC32C + { + public const int ChecksumSize = 4; + private const uint Poly = 0x82F63B78u; + private static readonly uint[] Table; + + static CRC32C() + { +#if NETCOREAPP3_0 + if(Sse42.IsSupported) + return; +#endif + Table = new uint[16 * 256]; + for (uint i = 0; i < 256; i++) + { + uint res = i; + for (int t = 0; t < 16; t++) + { + for (int k = 0; k < 8; k++) + res = (res & 1) == 1 ? Poly ^ (res >> 1) : (res >> 1); + Table[t * 256 + i] = res; + } + } + } + + /// + /// Compute CRC32C for data + /// + /// input data + /// offset + /// length + /// CRC32C checksum + public static uint Compute(byte[] input, int offset, int length) + { + uint crcLocal = uint.MaxValue; +#if NETCOREAPP3_0 + if (Sse42.IsSupported) + { + var data = new ReadOnlySpan(input, offset, length); + int processed = 0; + if (Sse42.X64.IsSupported && data.Length > sizeof(ulong)) + { + processed = data.Length / sizeof(ulong) * sizeof(ulong); + var ulongs = MemoryMarshal.Cast(data.Slice(0, processed)); + ulong crclong = crcLocal; + for (int i = 0; i < ulongs.Length; i++) + { + crclong = Sse42.X64.Crc32(crclong, ulongs[i]); + } + + crcLocal = (uint)crclong; + } + else if (data.Length > sizeof(uint)) + { + processed = data.Length / sizeof(uint) * sizeof(uint); + var uints = MemoryMarshal.Cast(data.Slice(0, processed)); + for (int i = 0; i < uints.Length; i++) + { + crcLocal = Sse42.Crc32(crcLocal, uints[i]); + } + } + + for (int i = processed; i < data.Length; i++) + { + crcLocal = Sse42.Crc32(crcLocal, data[i]); + } + + return crcLocal ^ uint.MaxValue; + } +#endif + while (length >= 16) + { + var a = Table[(3 * 256) + input[offset + 12]] + ^ Table[(2 * 256) + input[offset + 13]] + ^ Table[(1 * 256) + input[offset + 14]] + ^ Table[(0 * 256) + input[offset + 15]]; + + var b = Table[(7 * 256) + input[offset + 8]] + ^ Table[(6 * 256) + input[offset + 9]] + ^ Table[(5 * 256) + input[offset + 10]] + ^ Table[(4 * 256) + input[offset + 11]]; + + var c = Table[(11 * 256) + input[offset + 4]] + ^ Table[(10 * 256) + input[offset + 5]] + ^ Table[(9 * 256) + input[offset + 6]] + ^ Table[(8 * 256) + input[offset + 7]]; + + var d = Table[(15 * 256) + ((byte)crcLocal ^ input[offset])] + ^ Table[(14 * 256) + ((byte)(crcLocal >> 8) ^ input[offset + 1])] + ^ Table[(13 * 256) + ((byte)(crcLocal >> 16) ^ input[offset + 2])] + ^ Table[(12 * 256) + ((crcLocal >> 24) ^ input[offset + 3])]; + + crcLocal = d ^ c ^ b ^ a; + offset += 16; + length -= 16; + } + while (--length >= 0) + crcLocal = Table[(byte)(crcLocal ^ input[offset++])] ^ crcLocal >> 8; + return crcLocal ^ uint.MaxValue; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/FastBitConverter.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/FastBitConverter.cs new file mode 100644 index 0000000..3693a02 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/FastBitConverter.cs @@ -0,0 +1,118 @@ +using System.Runtime.InteropServices; + +namespace LiteNetLib.Utils +{ + public static class FastBitConverter + { + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperDouble + { + [FieldOffset(0)] + public ulong Along; + + [FieldOffset(0)] + public double Adouble; + } + + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperFloat + { + [FieldOffset(0)] + public int Aint; + + [FieldOffset(0)] + public float Afloat; + } + + private static void WriteLittleEndian(byte[] buffer, int offset, ulong data) + { +#if BIGENDIAN + buffer[offset + 7] = (byte)(data); + buffer[offset + 6] = (byte)(data >> 8); + buffer[offset + 5] = (byte)(data >> 16); + buffer[offset + 4] = (byte)(data >> 24); + buffer[offset + 3] = (byte)(data >> 32); + buffer[offset + 2] = (byte)(data >> 40); + buffer[offset + 1] = (byte)(data >> 48); + buffer[offset ] = (byte)(data >> 56); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); + buffer[offset + 4] = (byte)(data >> 32); + buffer[offset + 5] = (byte)(data >> 40); + buffer[offset + 6] = (byte)(data >> 48); + buffer[offset + 7] = (byte)(data >> 56); +#endif + } + + private static void WriteLittleEndian(byte[] buffer, int offset, int data) + { +#if BIGENDIAN + buffer[offset + 3] = (byte)(data); + buffer[offset + 2] = (byte)(data >> 8); + buffer[offset + 1] = (byte)(data >> 16); + buffer[offset ] = (byte)(data >> 24); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); +#endif + } + + public static void WriteLittleEndian(byte[] buffer, int offset, short data) + { +#if BIGENDIAN + buffer[offset + 1] = (byte)(data); + buffer[offset ] = (byte)(data >> 8); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); +#endif + } + + public static void GetBytes(byte[] bytes, int startIndex, double value) + { + ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value }; + WriteLittleEndian(bytes, startIndex, ch.Along); + } + + public static void GetBytes(byte[] bytes, int startIndex, float value) + { + ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value }; + WriteLittleEndian(bytes, startIndex, ch.Aint); + } + + public static void GetBytes(byte[] bytes, int startIndex, short value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + public static void GetBytes(byte[] bytes, int startIndex, ushort value) + { + WriteLittleEndian(bytes, startIndex, (short)value); + } + + public static void GetBytes(byte[] bytes, int startIndex, int value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + public static void GetBytes(byte[] bytes, int startIndex, uint value) + { + WriteLittleEndian(bytes, startIndex, (int)value); + } + + public static void GetBytes(byte[] bytes, int startIndex, long value) + { + WriteLittleEndian(bytes, startIndex, (ulong)value); + } + + public static void GetBytes(byte[] bytes, int startIndex, ulong value) + { + WriteLittleEndian(bytes, startIndex, value); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/INetSerializable.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/INetSerializable.cs new file mode 100644 index 0000000..92f14be --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/INetSerializable.cs @@ -0,0 +1,8 @@ +namespace LiteNetLib.Utils +{ + public interface INetSerializable + { + void Serialize(NetDataWriter writer); + void Deserialize(NetDataReader reader); + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataReader.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataReader.cs new file mode 100644 index 0000000..f31e6f8 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataReader.cs @@ -0,0 +1,707 @@ +using System; +using System.Net; +using System.Text; + +namespace LiteNetLib.Utils +{ + public class NetDataReader + { + protected byte[] _data; + protected int _position; + protected int _dataSize; + private int _offset; + + public byte[] RawData + { + get { return _data; } + } + + public int RawDataSize + { + get { return _dataSize; } + } + + public int UserDataOffset + { + get { return _offset; } + } + + public int UserDataSize + { + get { return _dataSize - _offset; } + } + + public bool IsNull + { + get { return _data == null; } + } + + public int Position + { + get { return _position; } + } + + public bool EndOfData + { + get { return _position == _dataSize; } + } + + public int AvailableBytes + { + get { return _dataSize - _position; } + } + + public void SkipBytes(int count) + { + _position += count; + } + + public void SetSource(NetDataWriter dataWriter) + { + _data = dataWriter.Data; + _position = 0; + _offset = 0; + _dataSize = dataWriter.Length; + } + + public void SetSource(byte[] source) + { + _data = source; + _position = 0; + _offset = 0; + _dataSize = source.Length; + } + + public void SetSource(byte[] source, int offset) + { + _data = source; + _position = offset; + _offset = offset; + _dataSize = source.Length; + } + + public void SetSource(byte[] source, int offset, int maxSize) + { + _data = source; + _position = offset; + _offset = offset; + _dataSize = maxSize; + } + + public NetDataReader() + { + + } + + public NetDataReader(byte[] source) + { + SetSource(source); + } + + public NetDataReader(byte[] source, int offset) + { + SetSource(source, offset); + } + + public NetDataReader(byte[] source, int offset, int maxSize) + { + SetSource(source, offset, maxSize); + } + + #region GetMethods + public IPEndPoint GetNetEndPoint() + { + string host = GetString(1000); + int port = GetInt(); + return NetUtils.MakeEndPoint(host, port); + } + + public byte GetByte() + { + byte res = _data[_position]; + _position += 1; + return res; + } + + public sbyte GetSByte() + { + var b = (sbyte)_data[_position]; + _position++; + return b; + } + + public bool[] GetBoolArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new bool[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetBool(); + } + return arr; + } + + public ushort[] GetUShortArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new ushort[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetUShort(); + } + return arr; + } + + public short[] GetShortArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new short[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetShort(); + } + return arr; + } + + public long[] GetLongArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new long[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetLong(); + } + return arr; + } + + public ulong[] GetULongArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new ulong[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetULong(); + } + return arr; + } + + public int[] GetIntArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new int[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetInt(); + } + return arr; + } + + public uint[] GetUIntArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new uint[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetUInt(); + } + return arr; + } + + public float[] GetFloatArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new float[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetFloat(); + } + return arr; + } + + public double[] GetDoubleArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new double[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetDouble(); + } + return arr; + } + + public string[] GetStringArray() + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new string[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetString(); + } + return arr; + } + + public string[] GetStringArray(int maxStringLength) + { + ushort size = BitConverter.ToUInt16(_data, _position); + _position += 2; + var arr = new string[size]; + for (int i = 0; i < size; i++) + { + arr[i] = GetString(maxStringLength); + } + return arr; + } + + public bool GetBool() + { + bool res = _data[_position] > 0; + _position += 1; + return res; + } + + public char GetChar() + { + char result = BitConverter.ToChar(_data, _position); + _position += 2; + return result; + } + + public ushort GetUShort() + { + ushort result = BitConverter.ToUInt16(_data, _position); + _position += 2; + return result; + } + + public short GetShort() + { + short result = BitConverter.ToInt16(_data, _position); + _position += 2; + return result; + } + + public long GetLong() + { + long result = BitConverter.ToInt64(_data, _position); + _position += 8; + return result; + } + + public ulong GetULong() + { + ulong result = BitConverter.ToUInt64(_data, _position); + _position += 8; + return result; + } + + public int GetInt() + { + int result = BitConverter.ToInt32(_data, _position); + _position += 4; + return result; + } + + public uint GetUInt() + { + uint result = BitConverter.ToUInt32(_data, _position); + _position += 4; + return result; + } + + public float GetFloat() + { + float result = BitConverter.ToSingle(_data, _position); + _position += 4; + return result; + } + + public double GetDouble() + { + double result = BitConverter.ToDouble(_data, _position); + _position += 8; + return result; + } + + public string GetString(int maxLength) + { + int bytesCount = GetInt(); + if (bytesCount <= 0 || bytesCount > maxLength*2) + { + return string.Empty; + } + + int charCount = Encoding.UTF8.GetCharCount(_data, _position, bytesCount); + if (charCount > maxLength) + { + return string.Empty; + } + + string result = Encoding.UTF8.GetString(_data, _position, bytesCount); + _position += bytesCount; + return result; + } + + public string GetString() + { + int bytesCount = GetInt(); + if (bytesCount <= 0) + { + return string.Empty; + } + + string result = Encoding.UTF8.GetString(_data, _position, bytesCount); + _position += bytesCount; + return result; + } + + public ArraySegment GetRemainingBytesSegment() + { + ArraySegment segment = new ArraySegment(_data, _position, AvailableBytes); + _position = _data.Length; + return segment; + } + + public T Get() where T : INetSerializable, new() + { + var obj = new T(); + obj.Deserialize(this); + return obj; + } + + public byte[] GetRemainingBytes() + { + byte[] outgoingData = new byte[AvailableBytes]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); + _position = _data.Length; + return outgoingData; + } + + public void GetBytes(byte[] destination, int start, int count) + { + Buffer.BlockCopy(_data, _position, destination, start, count); + _position += count; + } + + public void GetBytes(byte[] destination, int count) + { + Buffer.BlockCopy(_data, _position, destination, 0, count); + _position += count; + } + + public sbyte[] GetSBytesWithLength() + { + int length = GetInt(); + sbyte[] outgoingData = new sbyte[length]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, length); + _position += length; + return outgoingData; + } + + public byte[] GetBytesWithLength() + { + int length = GetInt(); + byte[] outgoingData = new byte[length]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, length); + _position += length; + return outgoingData; + } + #endregion + + #region PeekMethods + + public byte PeekByte() + { + return _data[_position]; + } + + public sbyte PeekSByte() + { + return (sbyte)_data[_position]; + } + + public bool PeekBool() + { + return _data[_position] > 0; + } + + public char PeekChar() + { + return BitConverter.ToChar(_data, _position); + } + + public ushort PeekUShort() + { + return BitConverter.ToUInt16(_data, _position); + } + + public short PeekShort() + { + return BitConverter.ToInt16(_data, _position); + } + + public long PeekLong() + { + return BitConverter.ToInt64(_data, _position); + } + + public ulong PeekULong() + { + return BitConverter.ToUInt64(_data, _position); + } + + public int PeekInt() + { + return BitConverter.ToInt32(_data, _position); + } + + public uint PeekUInt() + { + return BitConverter.ToUInt32(_data, _position); + } + + public float PeekFloat() + { + return BitConverter.ToSingle(_data, _position); + } + + public double PeekDouble() + { + return BitConverter.ToDouble(_data, _position); + } + + public string PeekString(int maxLength) + { + int bytesCount = BitConverter.ToInt32(_data, _position); + if (bytesCount <= 0 || bytesCount > maxLength * 2) + { + return string.Empty; + } + + int charCount = Encoding.UTF8.GetCharCount(_data, _position + 4, bytesCount); + if (charCount > maxLength) + { + return string.Empty; + } + + string result = Encoding.UTF8.GetString(_data, _position + 4, bytesCount); + return result; + } + + public string PeekString() + { + int bytesCount = BitConverter.ToInt32(_data, _position); + if (bytesCount <= 0) + { + return string.Empty; + } + + string result = Encoding.UTF8.GetString(_data, _position + 4, bytesCount); + return result; + } + #endregion + + #region TryGetMethods + public bool TryGetByte(out byte result) + { + if (AvailableBytes >= 1) + { + result = GetByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetSByte(out sbyte result) + { + if (AvailableBytes >= 1) + { + result = GetSByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetBool(out bool result) + { + if (AvailableBytes >= 1) + { + result = GetBool(); + return true; + } + result = false; + return false; + } + + public bool TryGetChar(out char result) + { + if (AvailableBytes >= 2) + { + result = GetChar(); + return true; + } + result = '\0'; + return false; + } + + public bool TryGetShort(out short result) + { + if (AvailableBytes >= 2) + { + result = GetShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUShort(out ushort result) + { + if (AvailableBytes >= 2) + { + result = GetUShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetInt(out int result) + { + if (AvailableBytes >= 4) + { + result = GetInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUInt(out uint result) + { + if (AvailableBytes >= 4) + { + result = GetUInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetLong(out long result) + { + if (AvailableBytes >= 8) + { + result = GetLong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetULong(out ulong result) + { + if (AvailableBytes >= 8) + { + result = GetULong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetFloat(out float result) + { + if (AvailableBytes >= 4) + { + result = GetFloat(); + return true; + } + result = 0; + return false; + } + + public bool TryGetDouble(out double result) + { + if (AvailableBytes >= 8) + { + result = GetDouble(); + return true; + } + result = 0; + return false; + } + + public bool TryGetString(out string result) + { + if (AvailableBytes >= 4) + { + var bytesCount = PeekInt(); + if (AvailableBytes >= bytesCount + 4) + { + result = GetString(); + return true; + } + } + result = null; + return false; + } + + public bool TryGetStringArray(out string[] result) + { + ushort size; + if (!TryGetUShort(out size)) + { + result = null; + return false; + } + + result = new string[size]; + for (int i = 0; i < size; i++) + { + if (!TryGetString(out result[i])) + { + result = null; + return false; + } + } + + return true; + } + + public bool TryGetBytesWithLength(out byte[] result) + { + if (AvailableBytes >= 4) + { + var length = PeekInt(); + if (length >= 0 && AvailableBytes >= length + 4) + { + result = GetBytesWithLength(); + return true; + } + } + result = null; + return false; + } + #endregion + + public void Clear() + { + _position = 0; + _dataSize = 0; + _data = null; + } + } +} + diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataWriter.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataWriter.cs new file mode 100644 index 0000000..27268cb --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetDataWriter.cs @@ -0,0 +1,415 @@ +using System; +using System.Net; +using System.Text; + +namespace LiteNetLib.Utils +{ + public class NetDataWriter + { + protected byte[] _data; + protected int _position; + private const int InitialSize = 64; + private readonly bool _autoResize; + + public int Capacity + { + get { return _data.Length; } + } + + public NetDataWriter() : this(true, InitialSize) + { + } + + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) + { + } + + public NetDataWriter(bool autoResize, int initialSize) + { + _data = new byte[initialSize]; + _autoResize = autoResize; + } + + /// + /// Creates NetDataWriter from existing ByteArray + /// + /// Source byte array + /// Copy array to new location or use existing + public static NetDataWriter FromBytes(byte[] bytes, bool copy) + { + if (copy) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } + return new NetDataWriter(true, 0) {_data = bytes}; + } + + /// + /// Creates NetDataWriter from existing ByteArray (always copied data) + /// + /// Source byte array + /// Offset of array + /// Length of array + public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes, offset, length); + return netDataWriter; + } + + public static NetDataWriter FromString(string value) + { + var netDataWriter = new NetDataWriter(); + netDataWriter.Put(value); + return netDataWriter; + } + + public void ResizeIfNeed(int newSize) + { + int len = _data.Length; + if (len < newSize) + { + while (len < newSize) + len *= 2; + Array.Resize(ref _data, len); + } + } + + public void Reset(int size) + { + ResizeIfNeed(size); + _position = 0; + } + + public void Reset() + { + _position = 0; + } + + public byte[] CopyData() + { + byte[] resultData = new byte[_position]; + Buffer.BlockCopy(_data, 0, resultData, 0, _position); + return resultData; + } + + public byte[] Data + { + get { return _data; } + } + + public int Length + { + get { return _position; } + } + + public void Put(float value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(double value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(long value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(ulong value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(int value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(uint value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(char value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(ushort value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(short value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(sbyte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = (byte)value; + _position++; + } + + public void Put(byte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = value; + _position++; + } + + public void Put(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length); + Buffer.BlockCopy(data, offset, _data, _position, length); + _position += length; + } + + public void Put(byte[] data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length); + Buffer.BlockCopy(data, 0, _data, _position, data.Length); + _position += data.Length; + } + + public void PutSBytesWithLength(sbyte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length + 4); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 4, length); + _position += length + 4; + } + + public void PutSBytesWithLength(sbyte[] data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length + 4); + FastBitConverter.GetBytes(_data, _position, data.Length); + Buffer.BlockCopy(data, 0, _data, _position + 4, data.Length); + _position += data.Length + 4; + } + + public void PutBytesWithLength(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length + 4); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 4, length); + _position += length + 4; + } + + public void PutBytesWithLength(byte[] data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length + 4); + FastBitConverter.GetBytes(_data, _position, data.Length); + Buffer.BlockCopy(data, 0, _data, _position + 4, data.Length); + _position += data.Length + 4; + } + + public void Put(bool value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = (byte)(value ? 1 : 0); + _position++; + } + + public void PutArray(float[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 4 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(double[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 8 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(long[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 8 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(ulong[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 8 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(int[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 4 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(uint[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 4 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(ushort[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 2 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(short[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len * 2 + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(bool[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + if (_autoResize) + ResizeIfNeed(_position + len + 2); + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(string[] value) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + Put(len); + for (int i = 0; i < len; i++) + Put(value[i]); + } + + public void PutArray(string[] value, int maxLength) + { + ushort len = value == null ? (ushort)0 : (ushort)value.Length; + Put(len); + for (int i = 0; i < len; i++) + Put(value[i], maxLength); + } + + public void Put(IPEndPoint endPoint) + { + Put(endPoint.Address.ToString()); + Put(endPoint.Port); + } + + public void Put(string value) + { + if (string.IsNullOrEmpty(value)) + { + Put(0); + return; + } + + //put bytes count + int bytesCount = Encoding.UTF8.GetByteCount(value); + if (_autoResize) + ResizeIfNeed(_position + bytesCount + 4); + Put(bytesCount); + + //put string + Encoding.UTF8.GetBytes(value, 0, value.Length, _data, _position); + _position += bytesCount; + } + + public void Put(string value, int maxLength) + { + if (string.IsNullOrEmpty(value)) + { + Put(0); + return; + } + + int length = value.Length > maxLength ? maxLength : value.Length; + //calculate max count + int bytesCount = Encoding.UTF8.GetByteCount(value); + if (_autoResize) + ResizeIfNeed(_position + bytesCount + 4); + + //put bytes count + Put(bytesCount); + + //put string + Encoding.UTF8.GetBytes(value, 0, length, _data, _position); + + _position += bytesCount; + } + + public void Put(T obj) where T : INetSerializable + { + obj.Serialize(this); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetPacketProcessor.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetPacketProcessor.cs new file mode 100644 index 0000000..043d8ad --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetPacketProcessor.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; + +namespace LiteNetLib.Utils +{ + public class NetPacketProcessor + { + private static class HashCache + { + public static bool Initialized; + public static ulong Id; + } + + protected delegate void SubscribeDelegate(NetDataReader reader, object userData); + private readonly NetSerializer _netSerializer; + private readonly Dictionary _callbacks = new Dictionary(); + private readonly NetDataWriter _netDataWriter = new NetDataWriter(); + + public NetPacketProcessor() + { + _netSerializer = new NetSerializer(); + } + + public NetPacketProcessor(int maxStringLength) + { + _netSerializer = new NetSerializer(maxStringLength); + } + + //FNV-1 64 bit hash + protected virtual ulong GetHash() + { + if(HashCache.Initialized) + return HashCache.Id; + + ulong hash = 14695981039346656037UL; //offset + string typeName = typeof(T).FullName; + for (var i = 0; i < typeName.Length; i++) + { + hash = hash ^ typeName[i]; + hash *= 1099511628211UL; //prime + } + HashCache.Initialized = true; + HashCache.Id = hash; + return hash; + } + + protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) + { + var hash = reader.GetULong(); + SubscribeDelegate action; + if (!_callbacks.TryGetValue(hash, out action)) + { + throw new ParseException("Undefined packet in NetDataReader"); + } + return action; + } + + protected virtual void WriteHash(NetDataWriter writer) + { + writer.Put(GetHash()); + } + + /// + /// 注册包含在数据包里的新类型 + /// Register nested property type + /// + /// INetSerializable structure + /// True - if register successful, false - if type already registered + public bool RegisterNestedType() where T : struct, INetSerializable + { + return _netSerializer.RegisterNestedType(); + } + + /// + /// Register nested property type + /// + /// + /// + /// True - if register successful, false - if type already registered + public bool RegisterNestedType(Action writeDelegate, Func readDelegate) + { + return _netSerializer.RegisterNestedType(writeDelegate, readDelegate); + } + + /// + /// Register nested property type + /// + /// INetSerializable class + /// True - if register successful, false - if type already registered + public bool RegisterNestedType(Func constructor) where T : class, INetSerializable + { + return _netSerializer.RegisterNestedType(constructor); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + public void ReadAllPackets(NetDataReader reader) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadAllPackets(NetDataReader reader, object userData) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader, userData); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Malformed packet + public void ReadPacket(NetDataReader reader) + { + ReadPacket(reader, null); + } + + public void Send(NetPeer peer, T packet, DeliveryMethod options) where T : class, new() + { + _netDataWriter.Reset(); + Write(_netDataWriter, packet); + peer.Send(_netDataWriter, options); + } + + public void SendNetSerializable(NetPeer peer, T packet, DeliveryMethod options) where T : INetSerializable + { + _netDataWriter.Reset(); + WriteNetSerializable(_netDataWriter, packet); + peer.Send(_netDataWriter, options); + } + + public void Send(NetManager manager, T packet, DeliveryMethod options) where T : class, new() + { + _netDataWriter.Reset(); + Write(_netDataWriter, packet); + manager.SendToAll(_netDataWriter, options); + } + + public void SendNetSerializable(NetManager manager, T packet, DeliveryMethod options) where T : INetSerializable + { + _netDataWriter.Reset(); + WriteNetSerializable(_netDataWriter, packet); + manager.SendToAll(_netDataWriter, options); + } + + public void Write(NetDataWriter writer, T packet) where T : class, new() + { + WriteHash(writer); + _netSerializer.Serialize(writer, packet); + } + + public void WriteNetSerializable(NetDataWriter writer, T packet) where T : INetSerializable + { + WriteHash(writer); + packet.Serialize(writer); + } + + public byte[] Write(T packet) where T : class, new() + { + _netDataWriter.Reset(); + WriteHash(_netDataWriter); + _netSerializer.Serialize(_netDataWriter, packet); + return _netDataWriter.CopyData(); + } + + public byte[] WriteNetSerializable(T packet) where T : INetSerializable + { + _netDataWriter.Reset(); + WriteHash(_netDataWriter); + packet.Serialize(_netDataWriter); + return _netDataWriter.CopyData(); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadPacket(NetDataReader reader, object userData) + { + GetCallbackFromData(reader)(reader, userData); + } + + /// + /// Register and subscribe to packet receive event + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet intead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } + + /// + /// Register and subscribe to packet receive event (with userData) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet intead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } + + /// + /// 注册某个数据包到来的事件 + /// 该方法会重写之前的事件 + /// Register and subscribe to packet receive event + /// This metod will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } + + /// + /// Register and subscribe to packet receive event + /// This metod will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt); + }; + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference, (TUserData)userData); + }; + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference); + }; + } + + /// + /// Remove any subscriptions by type + /// + /// Packet type + /// true if remove is success + public bool RemoveSubscription() + { + return _callbacks.Remove(GetHash()); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetSerializer.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetSerializer.cs new file mode 100644 index 0000000..3a61fc6 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NetSerializer.cs @@ -0,0 +1,603 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Net; + +namespace LiteNetLib.Utils +{ + public class InvalidTypeException : ArgumentException + { + public InvalidTypeException() + { + } + + public InvalidTypeException(string message) : base(message) + { + } + + public InvalidTypeException(string message, Exception innerException) : base(message, innerException) + { + } + + public InvalidTypeException(string message, string paramName) : base(message, paramName) + { + } + + public InvalidTypeException(string message, string paramName, Exception innerException) : base(message, paramName, innerException) + { + } + } + + public class ParseException : Exception + { + public ParseException() + { + } + + public ParseException(string message) : base(message) + { + } + + public ParseException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public sealed class NetSerializer + { + private sealed class NestedType + { + public readonly NestedTypeWriter WriteDelegate; + public readonly NestedTypeReader ReadDelegate; + public readonly NestedTypeWriter ArrayWriter; + public readonly NestedTypeReader ArrayReader; + + public NestedType(NestedTypeWriter writeDelegate, NestedTypeReader readDelegate, NestedTypeWriter arrayWriter, NestedTypeReader arrayReader) + { + WriteDelegate = writeDelegate; + ReadDelegate = readDelegate; + ArrayWriter = arrayWriter; + ArrayReader = arrayReader; + } + } + + private delegate void NestedTypeWriter(NetDataWriter writer, object customObj); + private delegate object NestedTypeReader(NetDataReader reader); + + private sealed class ClassInfo + { + public static ClassInfo Instance; + private readonly Action[] _writeDelegate; + private readonly Action[] _readDelegate; + private readonly int _membersCount; + + public ClassInfo(List> readDelegates, List> writeDelegates) + { + _membersCount = readDelegates.Count; + _writeDelegate = writeDelegates.ToArray(); + _readDelegate = readDelegates.ToArray(); + } + + public void Write(T obj, NetDataWriter writer) + { + for (int i = 0; i < _membersCount; i++) + _writeDelegate[i](obj, writer); + } + + public void Read(T obj, NetDataReader reader) + { + for (int i = 0; i < _membersCount; i++) + _readDelegate[i](obj, reader); + } + } + + private static readonly HashSet BasicTypes = new HashSet + { + typeof(int), + typeof(uint), + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(long), + typeof(ulong), + typeof(string), + typeof(float), + typeof(double), + typeof(bool), + typeof(char), + typeof(IPEndPoint) + }; + + private readonly NetDataWriter _writer; + private readonly int _maxStringLength; + private readonly Dictionary _registeredNestedTypes; + + public NetSerializer() : this(0) + { + + } + + public NetSerializer(int maxStringLength) + { + _maxStringLength = maxStringLength; + _registeredNestedTypes = new Dictionary(); + _writer = new NetDataWriter(); + } + + private bool RegisterNestedTypeInternal(Func constructor) where T : INetSerializable + { + var t = typeof(T); + if (_registeredNestedTypes.ContainsKey(t)) + return false; + NestedType nestedType; + NestedTypeWriter nestedTypeWriter = (writer, obj) => ((T) obj).Serialize(writer); + NestedTypeWriter nestedTypeArrayWriter = (writer, arr) => + { + var typedArr = (T[]) arr; + writer.Put((ushort) typedArr.Length); + for (int i = 0; i < typedArr.Length; i++) + typedArr[i].Serialize(writer); + }; + + //struct + if (constructor == null) + { + nestedType = new NestedType( + nestedTypeWriter, + reader => + { + var instance = default(T); + instance.Deserialize(reader); + return instance; + }, + nestedTypeArrayWriter, + reader => + { + var typedArr = new T[reader.GetUShort()]; + for (int i = 0; i < typedArr.Length; i++) + typedArr[i].Deserialize(reader); + return typedArr; + }); + } + else //class + { + nestedType = new NestedType( + nestedTypeWriter, + reader => + { + var instance = constructor(); + instance.Deserialize(reader); + return instance; + }, + nestedTypeArrayWriter, + reader => + { + var typedArr = new T[reader.GetUShort()]; + for (int i = 0; i < typedArr.Length; i++) + { + typedArr[i] = constructor(); + typedArr[i].Deserialize(reader); + } + return typedArr; + }); + } + _registeredNestedTypes.Add(t, nestedType); + return true; + } + + /// + /// Register nested property type + /// + /// INetSerializable structure + /// True - if register successful, false - if type already registered + public bool RegisterNestedType() where T : struct, INetSerializable + { + return RegisterNestedTypeInternal(null); + } + + /// + /// Register nested property type + /// + /// INetSerializable class + /// True - if register successful, false - if type already registered + public bool RegisterNestedType(Func constructor) where T : class, INetSerializable + { + return RegisterNestedTypeInternal(constructor); + } + + /// + /// Register nested property type + /// + /// + /// + /// True - if register successful, false - if type already registered + public bool RegisterNestedType(Action writeDelegate, Func readDelegate) + { + var t = typeof(T); + if (BasicTypes.Contains(t) || _registeredNestedTypes.ContainsKey(t)) + return false; + + var rwDelegates = new NestedType( + (writer, obj) => writeDelegate(writer, (T)obj), + reader => readDelegate(reader), + (writer, arr) => + { + var typedArr = (T[])arr; + writer.Put((ushort)typedArr.Length); + for (int i = 0; i < typedArr.Length; i++) + writeDelegate(writer, typedArr[i]); + }, + reader => + { + var typedArr = new T[reader.GetUShort()]; + for (int i = 0; i < typedArr.Length; i++) + typedArr[i] = readDelegate(reader); + return typedArr; + }); + + _registeredNestedTypes.Add(t, rwDelegates); + return true; + } + + private static Func ExtractGetDelegate(MethodInfo info) + { + return (Func)Delegate.CreateDelegate(typeof(Func), info); + } + + private static Action ExtractSetDelegate(MethodInfo info) + { + return (Action)Delegate.CreateDelegate(typeof(Action), info); + } + + private ClassInfo RegisterInternal() + { + if (ClassInfo.Instance != null) + return ClassInfo.Instance; + + Type t = typeof(T); + var props = t.GetProperties( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.GetProperty | + BindingFlags.SetProperty); + var writeDelegates = new List>(); + var readDelegates = new List>(); + for (int i = 0; i < props.Length; i++) + { + var property = props[i]; + var propertyType = property.PropertyType; + bool isEnum = propertyType.IsEnum; + var getMethod = property.GetGetMethod(); + var setMethod = property.GetSetMethod(); + if (getMethod == null || setMethod == null) + continue; + + if (isEnum) + { + var underlyingType = Enum.GetUnderlyingType(propertyType); + if (underlyingType == typeof(byte)) + { + readDelegates.Add((inf, r) => + { + property.SetValue(inf, Enum.ToObject(propertyType, r.GetByte()), null); + }); + writeDelegates.Add((inf, w) => + { + w.Put((byte)property.GetValue(inf, null)); + }); + } + else if (underlyingType == typeof(int)) + { + readDelegates.Add((inf, r) => + { + property.SetValue(inf, Enum.ToObject(propertyType, r.GetInt()), null); + }); + writeDelegates.Add((inf, w) => + { + w.Put((int)property.GetValue(inf, null)); + }); + } + else + { + throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); + } + } + else if (propertyType == typeof(string)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + if (_maxStringLength <= 0) + { + readDelegates.Add((inf, r) => setDelegate(inf, r.GetString())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else + { + readDelegates.Add((inf, r) => setDelegate(inf, r.GetString(_maxStringLength))); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf), _maxStringLength)); + } + } + else if (propertyType == typeof(bool)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetBool())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(byte)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetByte())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(sbyte)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetSByte())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(short)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetShort())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(ushort)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetUShort())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(int)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetInt())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(uint)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetUInt())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(long)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetLong())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(ulong)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetULong())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(float)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetFloat())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(double)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetDouble())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(char)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetChar())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + else if (propertyType == typeof(IPEndPoint)) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetNetEndPoint())); + writeDelegates.Add((inf, w) => w.Put(getDelegate(inf))); + } + // Array types + else if (propertyType == typeof(string[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + if (_maxStringLength <= 0) + { + readDelegates.Add((inf, r) => setDelegate( inf, r.GetStringArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate( inf))); + } + else + { + readDelegates.Add((inf, r) => setDelegate(inf, r.GetStringArray(_maxStringLength))); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf), _maxStringLength)); + } + } + else if (propertyType == typeof(bool[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetBoolArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(byte[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetBytesWithLength())); + writeDelegates.Add((inf, w) => w.PutBytesWithLength(getDelegate(inf))); + } + else if (propertyType == typeof(short[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetShortArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(ushort[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetUShortArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(int[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetIntArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(uint[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetUIntArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(long[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetLongArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(ulong[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetULongArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(float[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetFloatArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else if (propertyType == typeof(double[])) + { + var setDelegate = ExtractSetDelegate(setMethod); + var getDelegate = ExtractGetDelegate(getMethod); + readDelegates.Add((inf, r) => setDelegate(inf, r.GetDoubleArray())); + writeDelegates.Add((inf, w) => w.PutArray(getDelegate(inf))); + } + else + { + NestedType registeredNestedType; + bool array = false; + + if (propertyType.IsArray) + { + array = true; + propertyType = propertyType.GetElementType(); + } + + if (_registeredNestedTypes.TryGetValue(propertyType, out registeredNestedType)) + { + if (array) //Array type serialize/deserialize + { + readDelegates.Add((inf, r) => property.SetValue(inf, registeredNestedType.ArrayReader(r), null)); + writeDelegates.Add((inf, w) => registeredNestedType.ArrayWriter(w, property.GetValue(inf, null))); + } + else //Simple + { + readDelegates.Add((inf, r) => property.SetValue(inf, registeredNestedType.ReadDelegate(r), null)); + writeDelegates.Add((inf, w) => registeredNestedType.WriteDelegate(w, property.GetValue(inf, null))); + } + } + else + { + throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); + } + } + } + ClassInfo.Instance = new ClassInfo(readDelegates, writeDelegates); + return ClassInfo.Instance; + } + + /// 's fields are not supported, or it has no fields + public void Register() + { + RegisterInternal(); + } + + /// + /// Reads packet with known type + /// + /// NetDataReader with packet + /// Returns packet if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public T Deserialize(NetDataReader reader) where T : class, new() + { + var info = RegisterInternal(); + var result = new T(); + try + { + info.Read(result, reader); + } + catch + { + return null; + } + return result; + } + + /// + /// Reads packet with known type (non alloc variant) + /// + /// NetDataReader with packet + /// Deserialization target + /// Returns true if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public bool Deserialize(NetDataReader reader, T target) where T : class, new() + { + var info = RegisterInternal(); + try + { + info.Read(target, reader); + } + catch + { + return false; + } + return true; + } + + /// + /// Serialize struct to NetDataWriter (fast) + /// + /// Serialization target NetDataWriter + /// Object to serialize + /// 's fields are not supported, or it has no fields + public void Serialize(NetDataWriter writer, T obj) where T : class, new() + { + RegisterInternal().Write(obj, writer); + } + + /// + /// Serialize struct to byte array + /// + /// Object to serialize + /// byte array with serialized data + public byte[] Serialize(T obj) where T : class, new() + { + _writer.Reset(); + Serialize(_writer, obj); + return _writer.CopyData(); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpPacket.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpPacket.cs new file mode 100644 index 0000000..52afdd5 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpPacket.cs @@ -0,0 +1,428 @@ +using System; + +namespace LiteNetLib.Utils +{ + /// + /// Represents RFC4330 SNTP packet used for communication to and from a network time server. + /// + /// + /// + /// Most applications should just use the property. + /// + /// + /// The same data structure represents both request and reply packets. + /// Request and reply differ in which properties are set and to what values. + /// + /// + /// The only real property is . + /// All other properties read from and write to the underlying byte array + /// with the exception of , + /// which is not part of the packet on network and it is instead set locally after receiving the packet. + /// + /// + /// Copied from GuerrillaNtp project + /// with permission from Robert Vazan (@robertvazan) under MIT license, see https://github.com/RevenantX/LiteNetLib/pull/236 + /// + /// + public class NtpPacket + { + private static readonly DateTime Epoch = new DateTime(1900, 1, 1); + + /// + /// Gets RFC4330-encoded SNTP packet. + /// + /// + /// Byte array containing RFC4330-encoded SNTP packet. It is at least 48 bytes long. + /// + /// + /// This is the only real property. All other properties except + /// read from or write to this byte array. + /// + public byte[] Bytes { get; private set; } + + /// + /// Gets the leap second indicator. + /// + /// + /// Leap second warning, if any. Special value + /// indicates unsynchronized server clock. + /// Default is . + /// + /// + /// Only servers fill in this property. Clients can consult this property for possible leap second warning. + /// + public NtpLeapIndicator LeapIndicator + { + get { return (NtpLeapIndicator)((Bytes[0] & 0xC0) >> 6); } + } + + /// + /// Gets or sets protocol version number. + /// + /// + /// SNTP protocol version. Default is 4, which is the latest version at the time of this writing. + /// + /// + /// In request packets, clients should leave this property at default value 4. + /// Servers usually reply with the same protocol version. + /// + public int VersionNumber + { + get { return (Bytes[0] & 0x38) >> 3; } + private set { Bytes[0] = (byte)((Bytes[0] & ~0x38) | value << 3); } + } + + /// + /// Gets or sets SNTP packet mode, i.e. whether this is client or server packet. + /// + /// + /// SNTP packet mode. Default is in newly created packets. + /// Server reply should have this property set to . + /// + public NtpMode Mode + { + get { return (NtpMode)(Bytes[0] & 0x07); } + private set { Bytes[0] = (byte)((Bytes[0] & ~0x07) | (int)value); } + } + + /// + /// Gets server's distance from the reference clock. + /// + /// + /// + /// Distance from the reference clock. This property is set only in server reply packets. + /// Servers connected directly to reference clock hardware set this property to 1. + /// Statum number is incremented by 1 on every hop down the NTP server hierarchy. + /// + /// + /// Special value 0 indicates that this packet is a Kiss-o'-Death message + /// with kiss code stored in . + /// + /// + public int Stratum { get { return Bytes[1]; } } + + /// + /// Gets server's preferred polling interval. + /// + /// + /// Polling interval in log2 seconds, e.g. 4 stands for 16s and 17 means 131,072s. + /// + public int Poll { get { return Bytes[2]; } } + + /// + /// Gets the precision of server clock. + /// + /// + /// Clock precision in log2 seconds, e.g. -20 for microsecond precision. + /// + public int Precision { get { return (sbyte)Bytes[3]; } } + + /// + /// Gets the total round-trip delay from the server to the reference clock. + /// + /// + /// Round-trip delay to the reference clock. Normally a positive value smaller than one second. + /// + public TimeSpan RootDelay { get { return GetTimeSpan32(4); } } + + /// + /// Gets the estimated error in time reported by the server. + /// + /// + /// Estimated error in time reported by the server. Normally a positive value smaller than one second. + /// + public TimeSpan RootDispersion { get { return GetTimeSpan32(8); } } + + /// + /// Gets the ID of the time source used by the server or Kiss-o'-Death code sent by the server. + /// + /// + /// + /// ID of server's time source or Kiss-o'-Death code. + /// Purpose of this property depends on value of property. + /// + /// + /// Stratum 1 servers write here one of several special values that describe the kind of hardware clock they use. + /// + /// + /// Stratum 2 and lower servers set this property to IPv4 address of their upstream server. + /// If upstream server has IPv6 address, the address is hashed, because it doesn't fit in this property. + /// + /// + /// When server sets to special value 0, + /// this property contains so called kiss code that instructs the client to stop querying the server. + /// + /// + public uint ReferenceId { get { return GetUInt32BE(12); } } + + /// + /// Gets or sets the time when the server clock was last set or corrected. + /// + /// + /// Time when the server clock was last set or corrected or null when not specified. + /// + /// + /// This Property is usually set only by servers. It usually lags server's current time by several minutes, + /// so don't use this property for time synchronization. + /// + public DateTime? ReferenceTimestamp { get { return GetDateTime64(16); } } + + /// + /// Gets or sets the time when the client sent its request. + /// + /// + /// This property is null in request packets. + /// In reply packets, it is the time when the client sent its request. + /// Servers copy this value from + /// that they find in received request packet. + /// + /// + /// + public DateTime? OriginTimestamp { get { return GetDateTime64(24); } } + + /// + /// Gets or sets the time when the request was received by the server. + /// + /// + /// This property is null in request packets. + /// In reply packets, it is the time when the server received client request. + /// + /// + /// + public DateTime? ReceiveTimestamp { get { return GetDateTime64(32); } } + + /// + /// Gets or sets the time when the packet was sent. + /// + /// + /// Time when the packet was sent. It should never be null. + /// Default value is . + /// + /// + /// This property must be set by both clients and servers. + /// + /// + /// + public DateTime? TransmitTimestamp { get { return GetDateTime64(40); } private set { SetDateTime64(40, value); } } + + /// + /// Gets or sets the time of reception of response SNTP packet on the client. + /// + /// + /// Time of reception of response SNTP packet on the client. It is null in request packets. + /// + /// + /// This property is not part of the protocol and has to be set when reply packet is received. + /// + /// + /// + public DateTime? DestinationTimestamp { get; private set; } + + /// + /// Gets the round-trip time to the server. + /// + /// + /// Time the request spent travelling to the server plus the time the reply spent travelling back. + /// This is calculated from timestamps in the packet as (t1 - t0) + (t3 - t2) + /// where t0 is , + /// t1 is , + /// t2 is , + /// and t3 is . + /// This property throws an exception in request packets. + /// + public TimeSpan RoundTripTime + { + get + { + CheckTimestamps(); + return (ReceiveTimestamp.Value - OriginTimestamp.Value) + (DestinationTimestamp.Value - TransmitTimestamp.Value); + } + } + + /// + /// Gets the offset that should be added to local time to synchronize it with server time. + /// + /// + /// Time difference between server and client. It should be added to local time to get server time. + /// It is calculated from timestamps in the packet as 0.5 * ((t1 - t0) - (t3 - t2)) + /// where t0 is , + /// t1 is , + /// t2 is , + /// and t3 is . + /// This property throws an exception in request packets. + /// + public TimeSpan CorrectionOffset + { + get + { + CheckTimestamps(); + return TimeSpan.FromTicks(((ReceiveTimestamp.Value - OriginTimestamp.Value) - (DestinationTimestamp.Value - TransmitTimestamp.Value)).Ticks / 2); + } + } + + /// + /// Initializes default request packet. + /// + /// + /// Properties and + /// are set appropriately for request packet. Property + /// is set to . + /// + public NtpPacket() : this(new byte[48]) + { + Mode = NtpMode.Client; + VersionNumber = 4; + TransmitTimestamp = DateTime.UtcNow; + } + + /// + /// Initializes packet from received data. + /// + internal NtpPacket(byte[] bytes) + { + if (bytes.Length < 48) + throw new ArgumentException("SNTP reply packet must be at least 48 bytes long.", "bytes"); + Bytes = bytes; + } + + /// + /// Initializes packet from data received from a server. + /// + /// Data received from the server. + /// Utc time of reception of response SNTP packet on the client. + /// + public static NtpPacket FromServerResponse(byte[] bytes, DateTime destinationTimestamp) + { + var packet = new NtpPacket(bytes); + packet.DestinationTimestamp = destinationTimestamp; + return packet; + } + + internal void ValidateRequest() + { + if (Mode != NtpMode.Client) + throw new InvalidOperationException("This is not a request SNTP packet."); + if (VersionNumber == 0) + throw new InvalidOperationException("Protocol version of the request is not specified."); + if (TransmitTimestamp == null) + throw new InvalidOperationException("TransmitTimestamp must be set in request packet."); + } + + internal void ValidateReply() + { + if (Mode != NtpMode.Server) + throw new InvalidOperationException("This is not a reply SNTP packet."); + if (VersionNumber == 0) + throw new InvalidOperationException("Protocol version of the reply is not specified."); + if (Stratum == 0) + throw new InvalidOperationException(string.Format("Received Kiss-o'-Death SNTP packet with code 0x{0:x}.", ReferenceId)); + if (LeapIndicator == NtpLeapIndicator.AlarmCondition) + throw new InvalidOperationException("SNTP server has unsynchronized clock."); + CheckTimestamps(); + } + + private void CheckTimestamps() + { + if (OriginTimestamp == null) + throw new InvalidOperationException("Origin timestamp is missing."); + if (ReceiveTimestamp == null) + throw new InvalidOperationException("Receive timestamp is missing."); + if (TransmitTimestamp == null) + throw new InvalidOperationException("Transmit timestamp is missing."); + if (DestinationTimestamp == null) + throw new InvalidOperationException("Destination timestamp is missing."); + } + + private DateTime? GetDateTime64(int offset) + { + var field = GetUInt64BE(offset); + if (field == 0) + return null; + return new DateTime(Epoch.Ticks + Convert.ToInt64(field * (1.0 / (1L << 32) * 10000000.0))); + } + + private void SetDateTime64(int offset, DateTime? value) + { + SetUInt64BE(offset, value == null ? 0 : Convert.ToUInt64((value.Value.Ticks - Epoch.Ticks) * (0.0000001 * (1L << 32)))); + } + + private TimeSpan GetTimeSpan32(int offset) + { + return TimeSpan.FromSeconds(GetInt32BE(offset) / (double)(1 << 16)); + } + + private ulong GetUInt64BE(int offset) + { + return SwapEndianness(BitConverter.ToUInt64(Bytes, offset)); + } + + private void SetUInt64BE(int offset, ulong value) + { + FastBitConverter.GetBytes(Bytes, offset, SwapEndianness(value)); + } + + private int GetInt32BE(int offset) + { + return (int)GetUInt32BE(offset); + } + + private uint GetUInt32BE(int offset) + { + return SwapEndianness(BitConverter.ToUInt32(Bytes, offset)); + } + + private static uint SwapEndianness(uint x) + { + return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24); + } + + private static ulong SwapEndianness(ulong x) + { + return ((ulong)SwapEndianness((uint)x) << 32) | SwapEndianness((uint)(x >> 32)); + } + } + + /// + /// Represents leap second warning from the server that instructs the client to add or remove leap second. + /// + /// + public enum NtpLeapIndicator + { + /// + /// No leap second warning. No action required. + /// + NoWarning, + + /// + /// Warns the client that the last minute of the current day has 61 seconds. + /// + LastMinuteHas61Seconds, + + /// + /// Warns the client that the last minute of the current day has 59 seconds. + /// + LastMinuteHas59Seconds, + + /// + /// Special value indicating that the server clock is unsynchronized and the returned time is unreliable. + /// + AlarmCondition + } + + /// + /// Describes SNTP packet mode, i.e. client or server. + /// + /// + public enum NtpMode + { + /// + /// Identifies client-to-server SNTP packet. + /// + Client = 3, + + /// + /// Identifies server-to-client SNTP packet. + /// + Server = 4, + } +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpRequest.cs b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpRequest.cs new file mode 100644 index 0000000..59cef85 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/LiteNetLib/Utils/NtpRequest.cs @@ -0,0 +1,142 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace LiteNetLib.Utils +{ + /// + /// Make NTP request. + /// + /// 1. Create the object by method. + /// + /// + /// 2. Use method to send requests. 3. Call to release the socket + /// AFTER you have received the response or some timeout. If you close the socket too early, you may miss the response. + /// + /// + /// 3. Call to release the socket AFTER you have received the response or some timeout. + /// If you close the socket too early, you may miss the response. + /// + /// + public sealed class NtpRequest : INetSocketListener + { + public const int DefaultPort = 123; + + private readonly NetSocket _socket; + private readonly Action _onRequestComplete; + private readonly IPEndPoint _ntpEndPoint; + + /// + /// Initialize object, open socket. + /// + /// NTP Server endpoint + /// callback (called from other thread!) + private NtpRequest(IPEndPoint endPoint, Action onRequestComplete) + { + _ntpEndPoint = endPoint; + _onRequestComplete = onRequestComplete; + + // Create and start socket + _socket = new NetSocket(this); + _socket.Bind(IPAddress.Any, IPAddress.IPv6Any, 0, false, endPoint.AddressFamily == AddressFamily.InterNetworkV6); + } + + /// + /// Create the requests for NTP server, open socket. + /// + /// NTP Server address. + /// callback (called from other thread!) + public static NtpRequest Create(IPEndPoint endPoint, Action onRequestComplete) + { + return new NtpRequest(endPoint, onRequestComplete); + } + + /// + /// Create the requests for NTP server (default port), open socket. + /// + /// NTP Server address. + /// callback (called from other thread!) + public static NtpRequest Create(IPAddress ipAddress, Action onRequestComplete) + { + IPEndPoint endPoint = new IPEndPoint(ipAddress, DefaultPort); + return Create(endPoint, onRequestComplete); + } + + /// + /// Create the requests for NTP server, open socket. + /// + /// NTP Server address. + /// port + /// callback (called from other thread!) + public static NtpRequest Create(string ntpServerAddress, int port, Action onRequestComplete) + { + IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); + return Create(endPoint, onRequestComplete); + } + + /// + /// Create the requests for NTP server (default port), open socket. + /// + /// NTP Server address. + /// callback (called from other thread!) + public static NtpRequest Create(string ntpServerAddress, Action onRequestComplete) + { + IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, DefaultPort); + return Create(endPoint, onRequestComplete); + } + + /// + /// Send request to the NTP server calls callback (if success). + /// In case of error the callbacke is called with null param. + /// + public void Send() + { + SocketError errorCode = 0; + var packet = new NtpPacket(); + packet.ValidateRequest(); // not necessary + byte[] sendData = packet.Bytes; + var sendCount = _socket.SendTo(sendData, 0, sendData.Length, _ntpEndPoint, ref errorCode); + if (errorCode != 0 || sendCount != sendData.Length) + { + _onRequestComplete(null); + } + } + + /// + /// Close socket. + /// + public void Close() + { + _socket.Close(); + } + + /// + /// Handle received data: transform bytes to NtpPacket, close socket and call the callback. + /// + void INetSocketListener.OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint) + { + DateTime destinationTimestamp = DateTime.UtcNow; + if (!remoteEndPoint.Equals(_ntpEndPoint)) + return; + + if (length < 48) + { + NetDebug.Write(NetLogLevel.Trace, "NTP response too short: {}", length); + _onRequestComplete(null); + return; + } + + NtpPacket packet = NtpPacket.FromServerResponse(data, destinationTimestamp); + try + { + packet.ValidateReply(); + } + catch (InvalidOperationException ex) + { + NetDebug.Write(NetLogLevel.Trace, "NTP response error: {}", ex.Message); + packet = null; + } + _onRequestComplete(packet); + } + } +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/NSmartProxy.Infrastructure.csproj b/src/NSmartProxy.Infrastructure/NSmartProxy.Infrastructure.csproj index 71f79dd..f61be2c 100644 --- a/src/NSmartProxy.Infrastructure/NSmartProxy.Infrastructure.csproj +++ b/src/NSmartProxy.Infrastructure/NSmartProxy.Infrastructure.csproj @@ -1,16 +1,37 @@ - netstandard2.0 + netstandard2.0;net6.0 + True - - + + + + + + TextTemplatingFileGenerator + NSPVersion.cs + + + + + + + + + + True + True + NSPVersion.tt + + + diff --git a/src/NSmartProxy.Infrastructure/NetworkUtil.cs b/src/NSmartProxy.Infrastructure/NetworkUtil.cs index 4201623..92db9a4 100644 --- a/src/NSmartProxy.Infrastructure/NetworkUtil.cs +++ b/src/NSmartProxy.Infrastructure/NetworkUtil.cs @@ -87,10 +87,16 @@ public static int[] FindAvailableTCPPorts(int startPort, int PortCount) continue; } foreach (IPEndPoint endPoint in endPoints) - { - if (endPoint.Port != port) continue; - isAvailable = false; - break; + {//判断规则 :0000或者三冒号打头,ip占用,才算占用,否则pass + //因为linux下 出现192.168.0.106:8787的记录,也会被误判为端口被占用 + if (endPoint.Address.ToString() == "0.0.0.0" || endPoint.Address.ToString() == ":::") + { + if (endPoint.Port == port) + { + isAvailable = false; + break; + } + } } } while (!isAvailable && port < IPEndPoint.MaxPort); @@ -127,7 +133,7 @@ public static List FindUnAvailableTCPPorts(List ports) ipGlobalProperties.GetActiveTcpListeners(); for (int i = ports.Count - 1; i > -1; i--) { - if (ports[i] > 65535 || ports[i] < 1|| _usedPorts.Contains(ports[i])) + if (ports[i] > 65535 || ports[i] < 1 || _usedPorts.Contains(ports[i])) { usedPortList.Add(ports[i]); ports.Remove(ports[i]); @@ -156,13 +162,12 @@ public static List FindUnAvailableTCPPorts(List ports) /// /// The first port to check /// The first available port - public static int FindNextAvailableUDPPort(int startPort) + public static int FindOneAvailableUDPPort(int startPort) { int port = startPort; bool isAvailable = true; - var mutex = new Mutex(false, - string.Concat("Global/", PortReleaseGuid)); + var mutex = new Mutex(false, PortReleaseGuid); mutex.WaitOne(); try { @@ -201,7 +206,7 @@ public static int FindNextAvailableUDPPort(int startPort) } - public static async Task ConnectAndSend(string addess, int port, Protocol protocol, byte[] data, bool isClose = false) + public static async Task ConnectAndSend(string addess, int port, ServerProtocol protocol, byte[] data, bool isClose = false) { TcpClient configClient = new TcpClient(); bool isConnected = false; diff --git a/src/NSmartProxy.Infrastructure/Shared/Global.cs b/src/NSmartProxy.Infrastructure/Shared/Global.cs index afc0850..e030370 100644 --- a/src/NSmartProxy.Infrastructure/Shared/Global.cs +++ b/src/NSmartProxy.Infrastructure/Shared/Global.cs @@ -1,4 +1,6 @@ -namespace NSmartProxy.Shared +using NSmartProxy.Interfaces; + +namespace NSmartProxy.Shared { /// /// 放一些全局公用的静态变量 @@ -6,16 +8,38 @@ public sealed class Global { public const string NO_TOKEN_STRING = "notoken"; - public const string NSmartProxyClientName = "NSmartProxy Client v0.8"; - public const string NSmartProxyServerName = "NSmartProxy Server v0.8"; + + public const string TOKEN_COOKIE_NAME = "NSPTK"; + //public const string NSmartProxyClientName = "NSmartProxy Client v1.1.1028"; + //public const string NSmartProxyServerName = "NSmartProxy Server v1.1.1028"; public const int ClientReconnectInterval = 3000;//客户端断线重连时间间隔(毫秒) public const int HeartbeatInterval = 30000; //心跳间隔(毫秒) public const int HeartbeatCheckInterval = 90000; //心跳检测间隔(毫秒) - public const int DefaultConnectTimeout = 30000; //默认连接超时时间 - public const int DefaultWriteAckTimeout = 10000;//调用具备ack确认协议的等待时间 - public const int DefaultPopClientTimeout = 30000; //反弹连接超时时间 + public const int DefaultConnectTimeout = 30000; //默认连接超时时间(毫秒) + public const int DefaultWriteAckTimeout = 10000;//调用具备ack确认协议的等待时间(毫秒) + public const int DefaultPopClientTimeout = 30000; //反弹连接超时时间(毫秒) public const int TokenExpiredMinutes = 60 * 24 * 30; //token过期时间(分钟)TODO 待生效 + + public const string NSPClientServiceDisplayName = "NSPClient";//windows服务显示名 + public const string NSPClientServiceName = "NSPClient";//windows服务名 + + public const int ClientUdpReceiveTimeout = 3000;//客户端udp接收超时时长(毫秒) + + public const int ClientTunnelBufferSize = 1024 * 1024 * 1024; //客户端数据包大小 + public const int ClientUdpBufferSize = 65535;//服务端udp数据包大小 + #region 服务端配置 + + public const int StartArrangedPort = 20000; + public const string NSPServerDisplayName = "NSPServer";//windows服务显示名 + public const string NSPServerServiceName = "NSPServer";//windows服务名 + + public const int ServerTunnelBufferSize = 1024 * 1024 * 1024;//服务端数据包大小 + public const int ServerUdpBufferSize = 65535;//服务端udp数据包大小 + + + public static INSmartLogger Logger; + #endregion } } \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/Shared/IDbOperator.cs b/src/NSmartProxy.Infrastructure/Shared/IDbOperator.cs index b311f4a..646ebf9 100644 --- a/src/NSmartProxy.Infrastructure/Shared/IDbOperator.cs +++ b/src/NSmartProxy.Infrastructure/Shared/IDbOperator.cs @@ -9,7 +9,10 @@ public interface IDbOperator : IDisposable void Insert(long key, string value); void Insert(string key, string value); void Update(long key, string value); + void UpdateByName(string userName,string newUserName, string value); List Select(int startIndex, int length); + string GetConfig(string userId); + void SetConfig(string userId,string config); string Get(long key); string Get(string key); void Delete(int index); @@ -17,5 +20,6 @@ public interface IDbOperator : IDisposable long GetLength(); void Close(); bool Exist(string key); + int GetCount(); } } \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/Shared/NSPVersion.cs b/src/NSmartProxy.Infrastructure/Shared/NSPVersion.cs new file mode 100644 index 0000000..81fb4ff --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Shared/NSPVersion.cs @@ -0,0 +1,7 @@ + +public sealed class NSPVersion +{ + public const string NO_TOKEN_STRING = "notoken"; + public const string NSmartProxyClientName = "NSmartProxy Client v1.4.8596.1967"; + public const string NSmartProxyServerName = "NSmartProxy Server v1.4.8596.1967"; +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/Shared/NSPVersion.tt b/src/NSmartProxy.Infrastructure/Shared/NSPVersion.tt new file mode 100644 index 0000000..ee15da4 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Shared/NSPVersion.tt @@ -0,0 +1,16 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<# + var v1 = DateTime.Now.Ticks.ToString().Substring(2, 4).TrimStart('0'); + var v2 = DateTime.Now.Ticks.ToString().Substring(6, 4).TrimStart('0'); +#> + +public sealed class NSPVersion +{ + public const string NO_TOKEN_STRING = "notoken"; + public const string NSmartProxyClientName = "NSmartProxy Client v1.4.<#=v1#>.<#=v2#>"; + public const string NSmartProxyServerName = "NSmartProxy Server v1.4.<#=v1#>.<#=v2#>"; +} \ No newline at end of file diff --git a/src/NSmartProxy.Infrastructure/Shared/SecurityTcpClient.cs b/src/NSmartProxy.Infrastructure/Shared/SecurityTcpClient.cs index f7dd234..7877f05 100644 --- a/src/NSmartProxy.Infrastructure/Shared/SecurityTcpClient.cs +++ b/src/NSmartProxy.Infrastructure/Shared/SecurityTcpClient.cs @@ -156,7 +156,7 @@ public async Task AuthorizeAsync() return null; } - var token = ASCIIEncoding.ASCII.GetString(tokenBytes); + var token = Encoding.ASCII.GetString(tokenBytes); //校验Token if (token == Global.NO_TOKEN_STRING) { diff --git a/src/NSmartProxy.Infrastructure/Snappy/Snappy.cs b/src/NSmartProxy.Infrastructure/Snappy/Snappy.cs new file mode 100644 index 0000000..60f88d5 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Snappy/Snappy.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.IO; + +namespace Snappy.Sharp +{ + public static class Snappy + { + internal const int LITERAL = 0; + internal const int COPY_1_BYTE_OFFSET = 1; // 3 bit length + 3 bits of offset in opcode + internal const int COPY_2_BYTE_OFFSET = 2; + internal const int COPY_4_BYTE_OFFSET = 3; + + public static int MaxCompressedLength(int sourceLength) + { + var compressor = new SnappyCompressor(); + return compressor.MaxCompressedLength(sourceLength); + } + + public static byte[] Compress(byte[] uncompressed) + { + var target = new SnappyCompressor(); + var result = new byte[target.MaxCompressedLength(uncompressed.Length)]; + var count = target.Compress(uncompressed, 0, uncompressed.Length, result); + return result.Take(count).ToArray(); + } + + public static void Compress(Stream uncompressed, SnappyStream compressed) + { + throw new NotImplementedException(); + } + + public static int GetUncompressedLength(byte[] compressed, int offset = 0) + { + var decompressor = new SnappyDecompressor(); + return decompressor.ReadUncompressedLength(compressed, offset)[0]; + } + + public static byte[] Uncompress(byte[] compressed) + { + throw new NotImplementedException(); + } + + public static void Uncompress(SnappyStream compressed, StreamWriter uncompressed) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NSmartProxy.Infrastructure/Snappy/SnappyCompressor.cs b/src/NSmartProxy.Infrastructure/Snappy/SnappyCompressor.cs new file mode 100644 index 0000000..014f179 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Snappy/SnappyCompressor.cs @@ -0,0 +1,454 @@ +using System; +using System.Diagnostics; + +namespace Snappy.Sharp +{ + public class SnappyCompressor + { + private const int BLOCK_LOG = 15; + private const int BLOCK_SIZE = 1 << BLOCK_LOG; + + private const int INPUT_MARGIN_BYTES = 15; + + private const int MAX_HASH_TABLE_BITS = 14; + private const int MAX_HASH_TABLE_SIZE = 1 << MAX_HASH_TABLE_BITS; + + private readonly Func FindMatchLength; + + public SnappyCompressor() : this(Utilities.NativeIntPtrSize()) + { + } + + internal SnappyCompressor(int intPtrBytes) + { + if (intPtrBytes == 4) + { + Debug.WriteLine("Using 32-bit optimized FindMatchLength"); + FindMatchLength = FindMatchLength32; + } + else if (intPtrBytes == 8) + { + Debug.WriteLine("Using 64-bit optimized FindMatchLength"); + FindMatchLength = FindMatchLength64; + } + else + { + Debug.WriteLine("Using unoptimized FindMatchLength"); + FindMatchLength = FindMatchLengthBasic; + } + } + + public int MaxCompressedLength(int sourceLength) + { + // So says the code from Google. + return 32 + sourceLength + sourceLength / 6; + } + + public int Compress(byte[] uncompressed, int uncompressedOffset, int uncompressedLength, byte[] compressed) + { + return Compress(uncompressed, uncompressedOffset, uncompressedLength, compressed, 0); + } + + public int Compress(byte[] uncompressed, int uncompressedOffset, int uncompressedLength, byte[] compressed, int compressedOffset) + { + int compressedIndex = WriteUncomressedLength(compressed, compressedOffset, uncompressedLength); + int headLength = compressedIndex - compressedOffset; + return headLength + CompressInternal(uncompressed, uncompressedOffset, uncompressedLength, compressed, compressedIndex); + } + + internal int CompressInternal(byte[] uncompressed, int uncompressedOffset, int uncompressedLength, byte[] compressed, int compressedOffset) + { + // first time through set to offset. + int compressedIndex = compressedOffset; + short[] hashTable = GetHashTable(uncompressedLength); + + for (int read = 0; read < uncompressedLength; read += BLOCK_SIZE) + { + // Get encoding table for compression + Array.Clear(hashTable, 0, hashTable.Length); + + compressedIndex = CompressFragment( + uncompressed, + uncompressedOffset + read, + Math.Min(uncompressedLength - read, BLOCK_SIZE), + compressed, + compressedIndex, + hashTable); + } + return compressedIndex - compressedOffset; + + } + + internal int CompressFragment(byte[] input, int inputOffset, int inputSize, byte[] output, int outputIndex, short[] hashTable) + { + // "ip" is the input pointer, and "op" is the output pointer. + int inputIndex = inputOffset; + Debug.Assert(inputSize <= BLOCK_SIZE); + Debug.Assert((hashTable.Length & (hashTable.Length - 1)) == 0, "hashTable size must be a power of 2"); + int shift = (int) (32 - Utilities.Log2Floor((uint)hashTable.Length)); + //DCHECK_EQ(static_cast(kuint32max >> shift), table_size - 1); + int inputEnd = inputOffset + inputSize; + int baseInputIndex = inputIndex; + // Bytes in [next_emit, ip) will be emitted as literal bytes. Or + // [next_emit, ip_end) after the main loop. + int nextEmitIndex = inputIndex; + + if (inputSize >= INPUT_MARGIN_BYTES) + { + int ipLimit = inputOffset + inputSize - INPUT_MARGIN_BYTES; + + uint currentIndexBytes = Utilities.GetFourBytes(input, ++inputIndex); + for (uint nextHash = Hash(currentIndexBytes, shift); ; ) + { + Debug.Assert(nextEmitIndex < inputIndex); + // The body of this loop calls EmitLiteral once and then EmitCopy one or + // more times. (The exception is that when we're close to exhausting + // the input we goto emit_remainder.) + // + // In the first iteration of this loop we're just starting, so + // there's nothing to copy, so calling EmitLiteral once is + // necessary. And we only start a new iteration when the + // current iteration has determined that a call to EmitLiteral will + // precede the next call to EmitCopy (if any). + // + // Step 1: Scan forward in the input looking for a 4-byte-long match. + // If we get close to exhausting the input then goto emit_remainder. + // + // Heuristic match skipping: If 32 bytes are scanned with no matches + // found, start looking only at every other byte. If 32 more bytes are + // scanned, look at every third byte, etc.. When a match is found, + // immediately go back to looking at every byte. This is a small loss + // (~5% performance, ~0.1% density) for compressible data due to more + // bookkeeping, but for non-compressible data (such as JPEG) it's a huge + // win since the compressor quickly "realizes" the data is incompressible + // and doesn't bother looking for matches everywhere. + // + // The "skip" variable keeps track of how many bytes there are since the + // last match; dividing it by 32 (ie. right-shifting by five) gives the + // number of bytes to move ahead for each iteration. + uint skip = 32; + + int nextIp = inputIndex; + int candidate; + do + { + inputIndex = nextIp; + uint hash = nextHash; + Debug.Assert(hash == Hash(Utilities.GetFourBytes(input, inputIndex), shift)); + nextIp = (int)(inputIndex + (skip++ >> 5)); + if (nextIp > ipLimit) + { + goto emit_remainder; + } + currentIndexBytes = Utilities.GetFourBytes(input, nextIp); + nextHash = Hash(currentIndexBytes, shift); + candidate = baseInputIndex + hashTable[hash]; + Debug.Assert(candidate >= baseInputIndex); + Debug.Assert(candidate < inputIndex); + + hashTable[hash] = (short)(inputIndex - baseInputIndex); + } while (Utilities.GetFourBytes(input, inputIndex) != Utilities.GetFourBytes(input, candidate)); + + // Step 2: A 4-byte match has been found. We'll later see if more + // than 4 bytes match. But, prior to the match, input + // bytes [next_emit, ip) are unmatched. Emit them as "literal bytes." + Debug.Assert(nextEmitIndex + 16 < inputEnd); + outputIndex = EmitLiteral(output, outputIndex, input, nextEmitIndex, inputIndex - nextEmitIndex, true); + + // Step 3: Call EmitCopy, and then see if another EmitCopy could + // be our next move. Repeat until we find no match for the + // input immediately after what was consumed by the last EmitCopy call. + // + // If we exit this loop normally then we need to call EmitLiteral next, + // though we don't yet know how big the literal will be. We handle that + // by proceeding to the next iteration of the main loop. We also can exit + // this loop via goto if we get close to exhausting the input. + uint candidateBytes = 0; + int insertTail; + + do + { + // We have a 4-byte match at ip, and no need to emit any + // "literal bytes" prior to ip. + int baseIndex = inputIndex; + int matched = 4 + FindMatchLength(input, candidate + 4, inputIndex + 4, inputEnd); + inputIndex += matched; + int offset = baseIndex - candidate; + //DCHECK_EQ(0, memcmp(baseIndex, candidate, matched)); + outputIndex = EmitCopy(output, outputIndex, offset, matched); + // We could immediately start working at ip now, but to improve + // compression we first update table[Hash(ip - 1, ...)]. + insertTail = inputIndex - 1; + nextEmitIndex = inputIndex; + if (inputIndex >= ipLimit) + { + goto emit_remainder; + } + uint prevHash = Hash(Utilities.GetFourBytes(input, insertTail), shift); + hashTable[prevHash] = (short)(inputIndex - baseInputIndex - 1); + uint curHash = Hash(Utilities.GetFourBytes(input, insertTail + 1), shift); + candidate = baseInputIndex + hashTable[curHash]; + candidateBytes = Utilities.GetFourBytes(input, candidate); + hashTable[curHash] = (short)(inputIndex - baseInputIndex); + } while (Utilities.GetFourBytes(input, insertTail + 1) == candidateBytes); + + nextHash = Hash(Utilities.GetFourBytes(input, insertTail + 2), shift); + ++inputIndex; + } + } + + emit_remainder: + // Emit the remaining bytes as a literal + if (nextEmitIndex < inputEnd) + { + outputIndex = EmitLiteral(output, outputIndex, input, nextEmitIndex, inputEnd - nextEmitIndex, false); + } + + return outputIndex; + } + + private static int EmitCopyLessThan64(byte[] output, int outputIndex, int offset, int length) + { + Debug.Assert( offset >= 0); + Debug.Assert( length <= 64); + Debug.Assert( length >= 4); + Debug.Assert( offset < 65536); + + if ((length < 12) && (offset < 2048)) { + int lenMinus4 = length - 4; + Debug.Assert(lenMinus4 < 8); // Must fit in 3 bits + output[outputIndex++] = (byte) (Snappy.COPY_1_BYTE_OFFSET | ((lenMinus4) << 2) | ((offset >> 8) << 5)); + output[outputIndex++] = (byte) (offset); + } + else { + output[outputIndex++] = (byte) (Snappy.COPY_2_BYTE_OFFSET | ((length - 1) << 2)); + output[outputIndex++] = (byte) (offset); + output[outputIndex++] = (byte) (offset >> 8); + } + return outputIndex; + } + + private static int EmitCopy(byte[] compressed, int compressedIndex, int offset, int length) + { + // Emit 64 byte copies but make sure to keep at least four bytes reserved + while (length >= 68) + { + compressedIndex = EmitCopyLessThan64(compressed, compressedIndex, offset, 64); + length -= 64; + } + + // Emit an extra 60 byte copy if have too much data to fit in one copy + if (length > 64) + { + compressedIndex = EmitCopyLessThan64(compressed, compressedIndex, offset, 60); + length -= 60; + } + + // Emit remainder + compressedIndex = EmitCopyLessThan64(compressed, compressedIndex, offset, length); + return compressedIndex; + } + + // Return the largest n such that + // + // s1[s1Index,n-1] == s1[s2Index,n-1] + // and n <= (s2Limit - s2Index). + // + // Does not read s2Limit or beyond. + // Does not read *(s1 + (s2_limit - s2)) or beyond. + // Requires that s2Limit >= s2. + private static int FindMatchLengthBasic(byte[] s1, int s1Index, int s2Index, int s2Limit) + { + Debug.Assert(s2Limit >= s2Index); + int matched = 0; + while (s2Index + matched < s2Limit && s1[s1Index + matched] == s1[s2Index + matched]) { + ++matched; + } + return matched; + } + + // 32-bit optimized version of above + private static int FindMatchLength32(byte[] s1, int s1Index, int s2Index, int s2Limit) + { + Debug.Assert(s2Limit >= s2Index); + + int matched = 0; + while (s2Index <= s2Limit - 4) + { + uint a = Utilities.GetFourBytes(s1, s2Index); + uint b = Utilities.GetFourBytes(s1, s1Index + matched); + + if (a == b) + { + s2Index += 4; + matched += 4; + } + else + { + uint c = a ^ b; + int matchingBits = (int)Utilities.NumberOfTrailingZeros(c); + matched += matchingBits >> 3; + return matched; + } + } + while (s2Index < s2Limit) + { + if (s1[s1Index+matched] == s1[s2Index]) + { + ++s2Index; + ++matched; + } + else + { + return matched; + } + } + return matched; + } + + + // 64-bit optimized version of above + private static int FindMatchLength64(byte[] s1, int s1Index, int s2Index, int s2Limit) + { + Debug.Assert(s2Limit >= s2Index); + + int matched = 0; + while (s2Index <= s2Limit - 8) + { + ulong a = Utilities.GetEightBytes(s1, s2Index); + ulong b = Utilities.GetEightBytes(s1, s1Index + matched); + + if (a == b) + { + s2Index += 8; + matched += 8; + } + else + { + ulong c = a ^ b; + // first get low order 32 bits, if all 0 then get high order as well. + int matchingBits = (int)Utilities.NumberOfTrailingZeros(c); + matched += matchingBits >> 3; + return matched; + } + } + while (s2Index < s2Limit) + { + if (s1[s1Index+matched] == s1[s2Index]) + { + ++s2Index; + ++matched; + } + else + { + return matched; + } + } + return matched; + } + + + internal int EmitLiteral(byte[] output, int outputIndex, byte[] literal, int literalIndex, int length, bool allowFastPath) + { + int n = length - 1; + outputIndex = EmitLiteralTag(output, outputIndex, n); + if (allowFastPath && length <= 16) + { + Utilities.UnalignedCopy64(literal, literalIndex, output, outputIndex); + Utilities.UnalignedCopy64(literal, literalIndex + 8, output, outputIndex + 8); + return outputIndex + length; + } + Buffer.BlockCopy(literal, literalIndex, output, outputIndex, length); + return outputIndex + length; + } + + internal int EmitLiteralTag(byte[] output, int outputIndex, int size) + { + if (size < 60) + { + output[outputIndex++] = (byte)(Snappy.LITERAL | (size << 2)); + } + else + { + int baseIndex = outputIndex; + outputIndex++; + // TODO: Java version is 'unrolled' here, C++ isn't. Should look into it? + int count = 0; + while (size > 0) + { + output[outputIndex++] = (byte)(size & 0xff); + size >>= 8; + count++; + } + Debug.Assert(count >= 1); + Debug.Assert(count <= 4); + output[baseIndex] = (byte)(Snappy.LITERAL | ((59 + count) << 2)); + } + return outputIndex; + } + + internal int GetHashTableSize(int inputSize) + { + // Use smaller hash table when input.size() is smaller, since we + // fill the table, incurring O(hash table size) overhead for + // compression, and if the input is short, we won't need that + // many hash table entries anyway. + Debug.Assert(MAX_HASH_TABLE_SIZE >= 256); + + int hashTableSize = 256; + // TODO: again, java version unrolled, but this time with note that it isn't faster + while (hashTableSize < MAX_HASH_TABLE_SIZE && hashTableSize < inputSize) + { + hashTableSize <<= 1; + } + Debug.Assert(0 == (hashTableSize & (hashTableSize - 1)), "hash must be power of two"); + Debug.Assert(hashTableSize <= MAX_HASH_TABLE_SIZE, "hash table too large"); + return hashTableSize; + } + + private static uint Hash(uint bytes, int shift) + { + const int kMul = 0x1e35a7bd; + return (bytes * kMul) >> shift; + } + + internal int WriteUncomressedLength(byte[] compressed, int compressedOffset, int uncompressedLength) + { + const int bitMask = 0x80; + if (uncompressedLength < 0) + throw new ArgumentException("uncompressedLength"); + + // A little-endian varint. + // From doc: + // Varints consist of a series of bytes, where the lower 7 bits are data and the upper bit is set iff there are more bytes to read. + // In other words, an uncompressed length of 64 would be stored as 0x40, and an uncompressed length of 2097150 (0x1FFFFE) would + // be stored as 0xFE 0XFF 0X7F + while (uncompressedLength >= bitMask) + { + compressed[compressedOffset++] = (byte)(uncompressedLength | bitMask); + uncompressedLength = uncompressedLength >> 7; + } + compressed[compressedOffset++] = (byte)(uncompressedLength); + + return compressedOffset; + } + + internal short[] GetHashTable(int inputLength) + { + // Use smaller hash table when input.size() is smaller, since we + // fill the table, incurring O(hash table size) overhead for + // compression, and if the input is short, we won't need that + // many hash table entries anyway. + Debug.Assert(MAX_HASH_TABLE_SIZE > 256); + int tableSize = 256; + while (tableSize < MAX_HASH_TABLE_SIZE && tableSize < inputLength) + { + tableSize <<= 1; + } + Debug.Assert((tableSize & (tableSize - 1)) == 0, "Table size not power of 2."); + Debug.Assert(tableSize <= MAX_HASH_TABLE_SIZE, "Table size too large."); + // TODO: C++/Java versions do this with a reusable buffer for efficiency. Probably also useful here. All that not allocating in a tight loop and all + return new short[tableSize]; + } + } +} diff --git a/src/NSmartProxy.Infrastructure/Snappy/SnappyDecompressor.cs b/src/NSmartProxy.Infrastructure/Snappy/SnappyDecompressor.cs new file mode 100644 index 0000000..abfb611 --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Snappy/SnappyDecompressor.cs @@ -0,0 +1,336 @@ +using System; +using System.Diagnostics; + +namespace Snappy.Sharp +{ + public class SnappyDecompressor + { + private const int MAX_INCREMENT_COPY_OVERFLOW = 10; + private const int bitMask = 0x80; + + public int[] ReadUncompressedLength(byte[] data, int offset) + { + int sum = 0, currentShift = 0; + while ((data[offset] & bitMask) != 0) + { + sum = UpdateSum(data, offset, currentShift, sum); + offset++; + currentShift += 7; + } + sum = UpdateSum(data, offset, currentShift, sum); + offset++; + return new[] { sum, offset }; + } + + private static int UpdateSum(byte[] data, int offset, int currentShift, int sum) + { + int nextValue = data[offset] & (bitMask - 1); + nextValue <<= currentShift; + sum += nextValue; + return sum; + } + + public byte[] Decompress(byte[] compressed, int compressedOffset, int compressedSize) + { + var sizeHeader = ReadUncompressedLength(compressed, compressedOffset); + var data = new byte[sizeHeader[0]]; + + Decompress(compressed, sizeHeader[1], compressedSize + compressedOffset - sizeHeader[1], data, 0, data.Length); + + return data; + } + public int Decompress(byte[] input, int inputOffset, int inputSize, byte[] output, int outputOffset, int outputLimit) + { + int ipLimit = inputOffset + inputSize; + int opIndex = outputOffset; + int ipIndex = inputOffset; + + while (ipIndex < ipLimit - 5) + { + byte opCode = input[ipIndex++]; + ushort entry = opLookupTable[opCode]; + byte trailerBytes = (byte) (entry >> 11); + + uint trailer = (BitConverter.ToUInt32(input, ipIndex) & wordmask[trailerBytes]); + + // advance the ipIndex past the op codes + ipIndex += entry >> 11; + int length = entry & 0xff; + + if ((opCode & 0x3) == Snappy.LITERAL) + { + int literalLength = (int) (length + trailer); + CopyLiteral(input, ipIndex, output, opIndex, literalLength); + ipIndex += literalLength; + opIndex += literalLength; + } + else + { + // copyOffset/256 is encoded in bits 8..10. By just fetching + // those bits, we get copyOffset (since the bit-field starts at + // bit 8). + int copyOffset = entry & 0x700; + copyOffset += (int)trailer; + + // inline to force hot-spot to keep inline + // + // Equivalent to incrementalCopy (below) except that it can write up to ten extra + // bytes after the end of the copy, and that it is faster. + // + // The main part of this loop is a simple copy of eight bytes at a time until + // we've copied (at least) the requested amount of bytes. However, if op and + // src are less than eight bytes apart (indicating a repeating pattern of + // length < 8), we first need to expand the pattern in order to get the correct + // results. For instance, if the buffer looks like this, with the eight-byte + // and patterns marked as intervals: + // + // abxxxxxxxxxxxx + // [------] src + // [------] op + // + // a single eight-byte copy from to will repeat the pattern once, + // after which we can move two bytes without moving : + // + // ababxxxxxxxxxx + // [------] src + // [------] op + // + // and repeat the exercise until the two no longer overlap. + // + // This allows us to do very well in the special case of one single byte + // repeated many times, without taking a big hit for more general cases. + // + // The worst case of extra writing past the end of the match occurs when + // op - src == 1 and len == 1; the last copy will read from byte positions + // [0..7] and write to [4..11], whereas it was only supposed to write to + // position 1. Thus, ten excess bytes. + // TODO: Java inlined this function manually. Investigate. + CopyCopy(output, length, opIndex, outputLimit, copyOffset); + opIndex += length; + } + } + + for (; ipIndex < ipLimit; ) { + int[] result = DecompressTagSlow(input, ipIndex, output, outputLimit, outputOffset, opIndex); + ipIndex = result[0]; + opIndex = result[1]; + } + + return opIndex; + } + + /** + * NOTE: from Java version, need to determine if true in .Net as well. + * This is a second copy of the inner loop of decompressTags used when near the end + * of the input. The key difference is the reading of the trailer bytes. The fast + * code does a blind read of the next 4 bytes as an int, and this code assembles + * the int byte-by-byte to assure that the array is not over run. The reason this + * code path is separate is the if condition to choose between these two seemingly + * small differences costs like 10-20% of the throughput. I'm hoping in future + * versions of hot-spot this code can be integrated into the main loop but for now + * it is worth the extra maintenance pain to get the extra 10-20%. + */ + private static int[] DecompressTagSlow(byte[] input, int ipIndex, byte[] output, int outputLimit, int outputOffset, int opIndex) + { + // read the op code + byte opCode = input[ipIndex++]; + ushort entry = opLookupTable[opCode]; + byte trailerBytes = (byte) (entry >> 11); + // + // Key difference here + // + uint trailer = 0; + if (trailerBytes >= 4) + trailer = (uint) ((input[ipIndex + 3] & 0xff) << 24); + if (trailerBytes >= 3) + trailer |= (uint) ((input[ipIndex + 2] & 0xff) << 16); + if (trailerBytes >= 2) + trailer |= (uint) ((input[ipIndex + 1] & 0xff) << 8); + if (trailerBytes >= 1) + trailer |= (uint) (input[ipIndex] & 0xff); + + // advance the ipIndex past the op codes + ipIndex += trailerBytes; + int length = entry & 0xff; + + if ((opCode & 0x3) == Snappy.LITERAL) { + int literalLength = (int) (length + trailer); + CopyLiteral(input, ipIndex, output, opIndex, literalLength); + ipIndex += literalLength; + opIndex += literalLength; + } + else { + // copyOffset/256 is encoded in bits 8..10. By just fetching + // those bits, we get copyOffset (since the bit-field starts at + // bit 8). + int copyOffset = entry & 0x700; + copyOffset += (int)trailer; + + // TODO: Java inlined this function manually. Investigate. + CopyCopy(output, length, opIndex, outputLimit, copyOffset); + opIndex += length; + } + return new int[] {ipIndex, opIndex}; + } + + private static void CopyLiteral(byte[] input, int ipIndex, byte[] output, int opIndex, int length) + { + Debug.Assert(length > 0); + Debug.Assert(ipIndex >= 0); + Debug.Assert(opIndex >= 0); + + int spaceLeft = output.Length - opIndex; + int readableBytes = input.Length - ipIndex; + + if (readableBytes < length || spaceLeft < length) + { + throw new InvalidOperationException("Corrupt literal length"); + } + if (length <= 16 && spaceLeft >= 16 && readableBytes >= 16) + { + Utilities.UnalignedCopy64(input, ipIndex, output, opIndex); + Utilities.UnalignedCopy64(input, ipIndex + 8, output, opIndex + 8); + } + else + { + int fastLength = (int)(length & 0xFFFFFFF8); + if (fastLength <= 64) + { + // copy long-by-long + for (int i = 0; i < fastLength; i += 8) + { + Utilities.UnalignedCopy64(input, ipIndex + i, output, opIndex + i); + } + + // copy byte-by-byte + int slowLength = length & 0x7; + // NOTE: This is not a manual array copy. We are copying an overlapping region + // and we want input data to repeat as it is recopied. see incrementalCopy below. + for (int i = 0; i < slowLength; i += 1) + { + output[opIndex + fastLength + i] = input[ipIndex + fastLength + i]; + } + } + else + { + Buffer.BlockCopy(input, ipIndex, output, opIndex, length); + } + } + } + + private static void CopyCopy(byte[] output, int length, int opIndex, int outputLimit, int copyOffset) + { + int spaceLeft = outputLimit - opIndex; + int srcIndex = opIndex - copyOffset; + + if (length <= 16 && copyOffset >= 8 && spaceLeft >= 16) + { + // Fast path, used for the majority (70-80%) of dynamic invocations. + Utilities.UnalignedCopy64(output, srcIndex, output, opIndex); + Utilities.UnalignedCopy64(output, srcIndex + 8, output, opIndex + 8); + } + else if (spaceLeft >= length + MAX_INCREMENT_COPY_OVERFLOW) + { + IncrementalCopyFastPath(output, srcIndex, opIndex, length); + } + else + { + IncrementalCopy(output, srcIndex, output, opIndex, length); + } + } + + /** + * Copy "len" bytes from "src" to "op", one byte at a time. Used for + * handling COPY operations where the input and output regions may + * overlap. For example, suppose: + * src == "ab" + * op == src + 2 + * len == 20 + * + * After incrementalCopy, the result will have + * eleven copies of "ab" + * ababababababababababab + * Note that this does not match the semantics of either memcpy() + * or memmove(). + */ + private static void IncrementalCopy(byte[] source, int srcIndex, byte[] output, int opIndex, int length) + { + Debug.Assert(source != null); + Debug.Assert(output != null); + // do <= because we do a postfix increment index but a prefix decrement of length + Debug.Assert(srcIndex + length <= source.Length); + Debug.Assert(opIndex + length <= output.Length); + + do + { + output[opIndex++] = source[srcIndex++]; + } while (--length > 0); + } + + private static void IncrementalCopyFastPath(byte[] output, int srcIndex, int opIndex, int length) + { + int copiedLength = 0; + while ((opIndex + copiedLength) - srcIndex < 8) + { + Utilities.UnalignedCopy64(output, srcIndex, output, opIndex + copiedLength); + copiedLength += (opIndex + copiedLength) - srcIndex; + } + + for (int i = 0; i < length - copiedLength; i += 8) + { + Utilities.UnalignedCopy64(output, srcIndex + i, output, opIndex + copiedLength + i); + } + } + + // Mapping from i in range [0,4] to a mask to extract the bottom 8*i bits + private static readonly uint[] wordmask = new uint[]{ + 0, 0xff, 0xffff, 0xffffff, 0xffffffff + }; + + // Data stored per entry in lookup table: + // Range Bits-used Description + // ------------------------------------ + // 1..64 0..7 Literal/copy length encoded in opcode byte + // 0..7 8..10 Copy offset encoded in opcode byte / 256 + // 0..4 11..13 Extra bytes after opcode + // + // We use eight bits for the length even though 7 would have sufficed + // because of efficiency reasons: + // (1) Extracting a byte is faster than a bit-field + // (2) It properly aligns copy offset so we do not need a <<8 + private static readonly ushort[] opLookupTable = new ushort[]{ + 0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, + 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, + 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, + 0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008, + 0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a, + 0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c, + 0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e, + 0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010, + 0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012, + 0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014, + 0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016, + 0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018, + 0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a, + 0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c, + 0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e, + 0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020, + 0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022, + 0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024, + 0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026, + 0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028, + 0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a, + 0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c, + 0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e, + 0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030, + 0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032, + 0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034, + 0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036, + 0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038, + 0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a, + 0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c, + 0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e, + 0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040 + }; + } +} diff --git a/src/NSmartProxy.Infrastructure/Snappy/SnappyStream.cs b/src/NSmartProxy.Infrastructure/Snappy/SnappyStream.cs new file mode 100644 index 0000000..512134b --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Snappy/SnappyStream.cs @@ -0,0 +1,311 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace Snappy.Sharp +{ + // Modeled after System.IO.Compression.DeflateStream in the framework + public class SnappyStream : Stream + { + private const int BLOCK_LOG = 15; + private const int BLOCK_SIZE = 1 << BLOCK_LOG; + + private Stream stream; + private readonly CompressionMode compressionMode; + private readonly bool leaveStreamOpen; + private readonly bool writeChecksums; + private static readonly byte[] StreamHeader = new byte[] { (byte)'s', (byte)'N', (byte)'a', (byte)'P', (byte)'p', (byte)'Y'}; + private const byte StreamIdentifier = 0xff; + private const byte CompressedType = 0x00; + private const byte UncompressedType = 0x01; + + // allocate a 64kB buffer for the (de)compressor to use + private readonly byte[] internalBuffer = new byte[1<<(BLOCK_LOG + 1)]; + private int internalBufferIndex = 0; + private int internalBufferLength = 0; + + private readonly SnappyCompressor compressor; + private readonly SnappyDecompressor decompressor; + + /// + /// Initializes a new instance of the class. + /// + /// The stream. + /// The compression mode. + public SnappyStream(Stream s, CompressionMode mode) : this(s, mode, false, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The stream. + /// The compression mode. + /// If set to true leaves the stream open when complete. + /// true if checksums should be written to the stream + public SnappyStream(Stream s, CompressionMode mode, bool leaveOpen, bool checksum) + { + stream = s; + compressionMode = mode; + leaveStreamOpen = leaveOpen; + writeChecksums = checksum; + + if (compressionMode == CompressionMode.Decompress) + { + if (!stream.CanRead) + throw new InvalidOperationException("Trying to decompress and underlying stream not readable."); + + decompressor = new SnappyDecompressor(); + + CheckStreamIdentifier(); + CheckStreamHeader(); + } + if (compressionMode == CompressionMode.Compress) + { + if (!stream.CanWrite) + throw new InvalidOperationException("Trying to compress and underlying stream is not writable."); + + compressor = new SnappyCompressor(); + + stream.WriteByte(StreamIdentifier); + stream.Write(StreamHeader, 0, StreamHeader.Length); + } + } + + /// + /// Provides access to the underlying (compressed) . + /// + public Stream BaseStream { get { return stream; } } + + public override bool CanRead + { + get { return stream != null && compressionMode == CompressionMode.Decompress && stream.CanRead; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return stream != null && compressionMode == CompressionMode.Compress && stream.CanWrite; } + } + + public override void Flush() + { + if (stream != null) + stream.Flush(); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotImplementedException(); + } + public override int EndRead(IAsyncResult asyncResult) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (compressionMode != CompressionMode.Decompress || decompressor == null) + throw new InvalidOperationException("Cannot read if not set to decompression mode."); + + int readCount = 0; + int firstByte = stream.ReadByte(); + // first byte can indicate stream header, we just read it and move on. + if (firstByte == StreamIdentifier) + { + CheckStreamHeader(); + } + else if (firstByte == UncompressedType) + { + var length = GetChunkUncompressedLength(); + readCount = ProcessRemainingInternalBuffer(buffer, offset, count); + if (readCount != count) + { + stream.Read(internalBuffer, 0, length); + Array.Copy(internalBuffer, 0, buffer, offset, count - readCount); + internalBufferIndex = count - readCount; + internalBufferLength = length; + } + } + else if (firstByte == CompressedType) + { + var length = GetChunkUncompressedLength(); + count = ProcessRemainingInternalBuffer(buffer, offset, count); + + // we at most have 64kb in the buffer to read + byte[] tempBuffer = new byte[1 << (BLOCK_LOG + 1)]; + stream.Read(tempBuffer, 0, tempBuffer.Length); + + decompressor.Decompress(tempBuffer, 0, tempBuffer.Length, internalBuffer, 0, length); + + Array.Copy(internalBuffer, 0, buffer, offset, count); + internalBufferIndex = count; + internalBufferLength = length; + } + else if (firstByte > 0x2 && firstByte < 0x7f) + { + throw new InvalidOperationException("Found unskippable chunk type that cannot be undertood."); + } + else + { + // getting the length and skipping the data. + var length = GetChunkUncompressedLength(); + stream.Seek(length, SeekOrigin.Current); + readCount += length; + } + return readCount; + } + + private int ProcessRemainingInternalBuffer(byte[] buffer, int offset, int count) + { + if (internalBufferLength - internalBufferIndex > count) + { + Array.Copy(internalBuffer, internalBufferIndex, buffer, offset, count); + internalBufferIndex += count; + } + else if (internalBufferLength > 0) + { + Array.Copy(internalBuffer, internalBufferIndex, buffer, offset, internalBufferLength - internalBufferIndex); + count -= (internalBufferLength - internalBufferIndex); + } + return count; + } + + private int GetChunkUncompressedLength() + { + int len1 = stream.ReadByte(); + int len2 = stream.ReadByte(); + int length = (len1 << 8) | len2; + if (length > BLOCK_SIZE) + throw new InvalidOperationException("Chunk length is too big."); + return length; + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotImplementedException(); + } + public override void EndWrite(IAsyncResult asyncResult) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (compressionMode != CompressionMode.Compress || compressor == null) + throw new InvalidOperationException("Cannot write if not set to compression mode."); + + if (buffer.Length < count) + throw new InvalidOperationException(); + + for (int i = 0; i < count; i += BLOCK_SIZE) + { + stream.WriteByte(CompressedType); + compressor.WriteUncomressedLength(buffer, 1, count); + int compressedLength = compressor.CompressInternal(buffer, offset, count, internalBuffer, 2); + stream.Write(internalBuffer, 0, compressedLength + 3); + } + } + + protected override void Dispose(bool disposing) + { + try + { + if (disposing && stream != null) + { + Flush(); + if (compressionMode == CompressionMode.Compress && stream != null) + { + // Make sure all data written + } + } + } + finally + { + try + { + if (disposing && !leaveStreamOpen && stream != null) + { + stream.Close(); + } + } + finally + { + stream = null; + base.Dispose(disposing); + } + } + } + + /// + /// This operation is not supported and always throws a . + /// + /// This operation is not supported on this stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// This operation is not supported and always throws a . + /// + /// This operation is not supported on this stream. + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// This property is not supported and always throws a . + /// + /// This property is not supported on this stream. + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + /// + /// This property is not supported and always throws a . + /// + /// This property is not supported on this stream. + public override long Position + { + get + { + throw new NotSupportedException(); + } + set + { + throw new NotSupportedException(); + } + } + + private void CheckStreamHeader() + { + byte[] heading = new byte[StreamHeader.Length]; + stream.Read(heading, 0, heading.Length); + for (int i = 1; i < heading.Length; i++) + { + if (heading[i] != StreamHeader[i]) + throw new InvalidDataException("Stream does not start with required header"); + } + } + + private void CheckStreamIdentifier() + { + int firstByte = stream.ReadByte(); + if (firstByte == -1) + throw new InvalidOperationException("Found EOF when trying to read header."); + if (firstByte != StreamIdentifier) + throw new InvalidOperationException("Invalid stream identifier found."); + } + + } +} diff --git a/src/NSmartProxy.Infrastructure/Snappy/Utilities.cs b/src/NSmartProxy.Infrastructure/Snappy/Utilities.cs new file mode 100644 index 0000000..982225a --- /dev/null +++ b/src/NSmartProxy.Infrastructure/Snappy/Utilities.cs @@ -0,0 +1,674 @@ +using System; +using System.Diagnostics; + +namespace Snappy.Sharp +{ + internal class Utilities + { + + public static unsafe int NativeIntPtrSize() + { + return sizeof(IntPtr); + } + + /// + /// Copies 64 bits (8 bytes) from source array starting at sourceIndex into dest array starting at destIndex. + /// + /// The source array. + /// Index to start copying. + /// The destination array. + /// Index to start writing. + /// The name comes from the original Snappy C++ source. I don't think there is a good way to look at + /// things in an aligned manner in the .NET Framework. + //[SecuritySafeCritical] + public unsafe static void UnalignedCopy64(byte[] source, int sourceIndex, byte[] dest, int destIndex) + { + Debug.Assert(sourceIndex > -1); + Debug.Assert(destIndex > -1); + Debug.Assert(sourceIndex + 7 < source.Length); + Debug.Assert(destIndex + 7 < dest.Length); + + fixed (byte* src = &source[sourceIndex], dst = &dest[destIndex]) + { + *((long*)dst) = *((long*)src); + } + } + + ///> + /// Reads 4 bytes from memory into a uint. Does not take host enianness into account. + /// + //[SecuritySafeCritical] + public unsafe static uint GetFourBytes(byte[] source, int index) + { + Debug.Assert(index > -1); + Debug.Assert(index + 3 < source.Length); + + fixed (byte* src = &source[index]) + { + return *((uint*)src); + } + } + + ///> + /// Reads 8 bytes from memory into a uint. Does not take host enianness into account. + /// + //[SecuritySafeCritical] + public unsafe static ulong GetEightBytes(byte[] source, int index) + { + Debug.Assert(index > -1); + Debug.Assert(index + 7 < source.Length); + + fixed (byte* src = &source[index]) + { + return *((ulong*)src); + } + } + + // Function from http://aggregate.org/MAGIC/ + public static uint Log2Floor(uint x) + { + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + // here Log2Floor(0) = 0 + return (NumberOfOnes(x >> 1)); + } + + // Function from http://aggregate.org/MAGIC/ + public static uint NumberOfLeadingZeros(uint x) + { + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return (sizeof(int) * 8 - NumberOfOnes(x)); + } + + // Function from http://aggregate.org/MAGIC/ + public static uint NumberOfTrailingZeros(uint x) + { + return NumberOfOnes((uint)((x & -x) - 1)); + } + + // Function from http://aggregate.org/MAGIC/ + public static uint NumberOfOnes(uint x) + { + x -= ((x >> 1) & 0x55555555); + x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); + x = (((x >> 4) + x) & 0x0f0f0f0f); + x += (x >> 8); + x += (x >> 16); + return (x & 0x0000003f); + } + + public static uint NumberOfTrailingZeros(ulong c) + { + uint matchingBits = NumberOfTrailingZeros((uint)c); + if (matchingBits == 32) + { + matchingBits += NumberOfTrailingZeros((uint)(c >> 32)); + } + return matchingBits; + } + + // Taken from Crc32.java in snappy-java project + public static uint CalculateCrc(byte[] b, int off, int len) + { + uint localCrc = 0; + while (len > 7) + { + uint c0 = b[off++] ^ localCrc; + uint c1 = b[off++] ^ (localCrc >>= 8); + uint c2 = b[off++] ^ (localCrc >>= 8); + uint c3 = b[off++] ^ (localCrc >>= 8); + localCrc = (T8_7[c0 & 0xff] ^ T8_6[c1 & 0xff]) + ^ (T8_5[c2 & 0xff] ^ T8_4[c3 & 0xff]); + + localCrc ^= (T8_3[b[off++] & 0xff] ^ T8_2[b[off++] & 0xff]) + ^ (T8_1[b[off++] & 0xff] ^ T8_0[b[off++] & 0xff]); + + len -= 8; + } + while (len > 0) + { + localCrc = (localCrc >> 8) ^ T8_0[(localCrc ^ b[off++]) & 0xff]; + len--; + } + + return localCrc; + } + + + static readonly uint[] T8_0 = new uint[]{ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 + }; + static readonly uint[] T8_1 = new uint[]{ + 0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, + 0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945, + 0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, + 0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD, + 0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, + 0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4, + 0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, + 0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C, + 0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, + 0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47, + 0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, + 0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF, + 0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, + 0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6, + 0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, + 0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E, + 0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, + 0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41, + 0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, + 0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9, + 0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, + 0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0, + 0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, + 0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78, + 0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, + 0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43, + 0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, + 0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB, + 0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, + 0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2, + 0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, + 0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A, + 0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, + 0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC, + 0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, + 0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004, + 0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, + 0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D, + 0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, + 0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185, + 0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, + 0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE, + 0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, + 0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306, + 0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, + 0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F, + 0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, + 0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287, + 0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, + 0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8, + 0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, + 0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600, + 0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, + 0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439, + 0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, + 0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781, + 0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, + 0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA, + 0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, + 0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502, + 0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, + 0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B, + 0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, + 0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483 + }; + static readonly uint[] T8_2 = new uint[]{ + 0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, + 0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469, + 0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, + 0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC, + 0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, + 0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3, + 0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, + 0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726, + 0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, + 0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D, + 0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, + 0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8, + 0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, + 0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7, + 0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, + 0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32, + 0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, + 0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0, + 0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, + 0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75, + 0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, + 0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A, + 0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, + 0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF, + 0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, + 0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4, + 0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, + 0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161, + 0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, + 0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E, + 0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, + 0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB, + 0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, + 0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A, + 0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, + 0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF, + 0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, + 0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0, + 0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, + 0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065, + 0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, + 0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E, + 0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, + 0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB, + 0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, + 0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4, + 0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, + 0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71, + 0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, + 0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3, + 0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, + 0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36, + 0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, + 0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79, + 0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, + 0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC, + 0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, + 0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7, + 0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, + 0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622, + 0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, + 0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D, + 0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, + 0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8 + }; + static readonly uint[] T8_3 = new uint[]{ + 0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, + 0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA, + 0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, + 0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C, + 0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, + 0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7, + 0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, + 0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11, + 0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, + 0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41, + 0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, + 0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7, + 0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, + 0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C, + 0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, + 0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A, + 0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, + 0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D, + 0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, + 0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB, + 0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, + 0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610, + 0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, + 0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6, + 0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, + 0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6, + 0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, + 0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040, + 0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, + 0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B, + 0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, + 0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D, + 0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, + 0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5, + 0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, + 0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213, + 0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, + 0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8, + 0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, + 0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E, + 0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, + 0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E, + 0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, + 0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698, + 0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, + 0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443, + 0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, + 0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5, + 0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, + 0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12, + 0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, + 0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4, + 0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, + 0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F, + 0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, + 0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9, + 0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, + 0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99, + 0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, + 0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F, + 0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, + 0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4, + 0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, + 0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842 + }; + static readonly uint[] T8_4 = new uint[]{ + 0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, + 0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44, + 0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, + 0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5, + 0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, + 0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97, + 0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, + 0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406, + 0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, + 0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13, + 0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, + 0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082, + 0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, + 0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0, + 0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, + 0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151, + 0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, + 0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA, + 0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, + 0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B, + 0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, + 0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539, + 0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, + 0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8, + 0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, + 0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD, + 0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, + 0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C, + 0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, + 0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E, + 0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, + 0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF, + 0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, + 0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18, + 0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, + 0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089, + 0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, + 0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB, + 0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, + 0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A, + 0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, + 0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F, + 0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, + 0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE, + 0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, + 0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C, + 0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, + 0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D, + 0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, + 0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6, + 0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, + 0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27, + 0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, + 0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065, + 0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, + 0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4, + 0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, + 0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1, + 0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, + 0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70, + 0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, + 0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532, + 0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, + 0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3 + }; + static readonly uint[] T8_5 = new uint[]{ + 0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, + 0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD, + 0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, + 0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2, + 0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, + 0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93, + 0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, + 0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C, + 0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, + 0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20, + 0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, + 0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F, + 0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, + 0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E, + 0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, + 0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201, + 0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, + 0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746, + 0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, + 0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59, + 0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, + 0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778, + 0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, + 0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67, + 0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, + 0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB, + 0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, + 0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4, + 0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, + 0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5, + 0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, + 0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA, + 0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, + 0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B, + 0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, + 0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364, + 0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, + 0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45, + 0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, + 0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A, + 0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, + 0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6, + 0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, + 0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9, + 0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, + 0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8, + 0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, + 0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7, + 0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, + 0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090, + 0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, + 0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F, + 0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, + 0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE, + 0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, + 0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1, + 0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, + 0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D, + 0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, + 0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02, + 0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, + 0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623, + 0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, + 0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C + }; + static readonly uint[] T8_6 = new uint[]{ + 0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, + 0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089, + 0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, + 0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA, + 0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, + 0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F, + 0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, + 0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C, + 0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, + 0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334, + 0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, + 0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67, + 0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, + 0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992, + 0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, + 0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1, + 0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, + 0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3, + 0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, + 0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0, + 0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, + 0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55, + 0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, + 0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006, + 0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, + 0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E, + 0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, + 0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D, + 0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, + 0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8, + 0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, + 0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB, + 0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, + 0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D, + 0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, + 0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E, + 0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, + 0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB, + 0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, + 0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988, + 0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, + 0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0, + 0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, + 0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093, + 0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, + 0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766, + 0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, + 0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35, + 0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, + 0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907, + 0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, + 0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454, + 0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, + 0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1, + 0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, + 0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2, + 0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, + 0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA, + 0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, + 0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9, + 0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, + 0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C, + 0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, + 0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F + }; + static readonly uint[] T8_7 = new uint[]{ + 0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, + 0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504, + 0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, + 0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE, + 0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, + 0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0, + 0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, + 0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A, + 0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, + 0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D, + 0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, + 0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447, + 0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, + 0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929, + 0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, + 0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3, + 0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, + 0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36, + 0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, + 0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC, + 0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, + 0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782, + 0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, + 0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358, + 0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, + 0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF, + 0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, + 0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75, + 0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, + 0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B, + 0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, + 0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1, + 0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, + 0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360, + 0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, + 0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA, + 0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, + 0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4, + 0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, + 0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E, + 0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, + 0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9, + 0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, + 0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223, + 0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, + 0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D, + 0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, + 0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97, + 0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, + 0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852, + 0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, + 0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88, + 0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, + 0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6, + 0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, + 0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C, + 0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, + 0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB, + 0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, + 0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911, + 0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, + 0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F, + 0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, + 0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5 + }; + } +} diff --git a/src/NSmartProxy.Infrastructure/StringUtil.cs b/src/NSmartProxy.Infrastructure/StringUtil.cs index 2d4a73f..277401f 100644 --- a/src/NSmartProxy.Infrastructure/StringUtil.cs +++ b/src/NSmartProxy.Infrastructure/StringUtil.cs @@ -3,9 +3,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; using NSmartProxy.Data; using NSmartProxy.Infrastructure; +using System.Threading; +using System.Threading.Tasks; +using Snappy.Sharp; namespace NSmartProxy { @@ -59,12 +63,12 @@ public static int DoubleBytesToInt(byte[] bytes) public static string ToASCIIString(this byte[] bytes) { - return System.Text.ASCIIEncoding.ASCII.GetString(bytes); + return System.Text.Encoding.ASCII.GetString(bytes); } public static byte[] ToASCIIBytes(this string str) { - return System.Text.ASCIIEncoding.ASCII.GetBytes(str); + return System.Text.Encoding.ASCII.GetBytes(str); } /// @@ -155,5 +159,133 @@ public static string[] Tail(this TextReader reader, int lineCount) return retVal; } + + /// + /// 在一个byte数组中查找另外一个byte数组的位置 + /// + /// + /// + /// + public static int SearchBytesFromBytes(byte[] haystack, byte[] needle) + { + for (int i = 0; i <= haystack.Length - needle.Length; i++) + { + if (Match(haystack, needle, i)) + { + return i; + } + } + return -1; + } + + public static bool Match(byte[] haystack, byte[] needle, int start) + { + if (needle.Length + start > haystack.Length) + { + return false; + } + else + { + for (int i = 0; i < needle.Length; i++) + { + if (needle[i] != haystack[i + start]) + { + return false; + } + } + return true; + } + } + + /// + /// 判断字符串是否是无小数点纯数字 + /// + /// + /// + public static bool IsNum(this string str) + { + for (int i = 0; i < str.Length; i++) + { + if (str[i] < '0' || str[i] > '9') + return false; + } + return true; + } + + // + // 使用snappy算法解压缩 + // + // + // < param name="offset"> + // + // < returns > + public static byte[] DecompressInSnappy(byte[] compressed, int offset, int length) + { + SnappyDecompressor sd = new SnappyDecompressor(); + try + { + return sd.Decompress(compressed, offset, length); + } + catch (Exception ex) + { + _ = ex; + //啥情況? + return null; + } + } + + // + // 使用snappy算法压缩 + // + // + // < param name="offset"> + // + // < returns > + public static CompressedBytes CompressInSnappy(byte[] uncompressed, int offset, int uncompressedLength) + { + SnappyCompressor sc = new SnappyCompressor(); + + //var bytes = Encoding.ASCII.GetBytes("HelloWor134ertegsdfgsfdgsdfgsdfgsfdgsdfgsdfgsdfgsdfgdsfgsdfgdsfgdfgdsfgld"); + byte[] outBytes = new byte[sc.MaxCompressedLength(uncompressed.Length)]; + + int actualLength = sc.Compress(uncompressed, 0, uncompressedLength, outBytes); + return new CompressedBytes() { ContentBytes = outBytes, Length = actualLength }; + } + + + //public async static Task CompressInSnappierAsync(Stream inputStream, Stream outputStream, CancellationToken ct) + //{ + // await _snappyCompressor.Value.CompressAsync(inputStream, outputStream,ct); + //} + + //public async static Task DecompressInSnappierAsync(Stream inputStream, Stream outputStream, CancellationToken ct) + //{ + // await _snappyCompressor.Value.DecompressAsync(inputStream, outputStream, ct); + //} + + //public static byte[] CompressInSnappier(byte[] inputBytes) + //{ + // return _snappyCompressor.Value.Compress(inputBytes); + //} + + //public static byte[] DecompressInSnappier(byte[] inputBytes) + //{ + // return _snappyCompressor.Value.Decompress(inputBytes); + //} + + internal static byte[] IntTo4Bytes(int length) + { + return BitConverter.GetBytes(length); + } + + + /// + /// 压缩专用对象 + /// + public class CompressedBytes + { + public int Length; + public byte[] ContentBytes; + } } } diff --git a/src/NSmartProxy.NUnitTest/CAGenerate.cs b/src/NSmartProxy.NUnitTest/CAGenerate.cs new file mode 100644 index 0000000..b73e1df --- /dev/null +++ b/src/NSmartProxy.NUnitTest/CAGenerate.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using NSmartProxy.Extension; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace NSmartProxy.NUnitTest +{ + public class CAGenerate + { + + [SetUp] + public void Setup() + { + } + [Test] + public void TestCAGen() + { + + var ca = CAGen.GenerateCA("shao"); + var export = ca.Export(X509ContentType.Pfx); + File.WriteAllBytes("c:\\test.pfx", export); + Assert.NotNull(ca); + } + + } +} diff --git a/src/NSmartProxy.NUnitTest/HttpAPI.cs b/src/NSmartProxy.NUnitTest/HttpAPI.cs index 818b9e9..8a3b046 100644 --- a/src/NSmartProxy.NUnitTest/HttpAPI.cs +++ b/src/NSmartProxy.NUnitTest/HttpAPI.cs @@ -49,5 +49,8 @@ private string[] GetLogFileInfo(int lastLines) return sr.Tail(lastLines); } } + + + } } \ No newline at end of file diff --git a/src/NSmartProxy.NUnitTest/NSmartProxy.NUnitTest.csproj b/src/NSmartProxy.NUnitTest/NSmartProxy.NUnitTest.csproj index 5b8d785..cca85d5 100644 --- a/src/NSmartProxy.NUnitTest/NSmartProxy.NUnitTest.csproj +++ b/src/NSmartProxy.NUnitTest/NSmartProxy.NUnitTest.csproj @@ -1,19 +1,20 @@ - + - netcoreapp2.2 + netcoreapp3.1 false - - - + + + + diff --git a/src/NSmartProxy.NUnitTest/P2PPunch.cs b/src/NSmartProxy.NUnitTest/P2PPunch.cs new file mode 100644 index 0000000..f3d2ed5 --- /dev/null +++ b/src/NSmartProxy.NUnitTest/P2PPunch.cs @@ -0,0 +1,66 @@ +using LiteNetLib; +using LiteNetLib.Utils; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace NSmartProxy.NUnitTest +{ + class P2PPunch + { + + public void Server() + { + EventBasedNetListener listener = new EventBasedNetListener(); + NetManager server = new NetManager(listener); + server.Start(9050 /* port */); + + listener.ConnectionRequestEvent += request => + { + //if (server.ConnectedPeersCount < 10 /* max connections */) + // request.AcceptIfKey("SomeConnectionKey"); + //else + // request.Reject(); + }; + + listener.PeerConnectedEvent += peer => + { + Console.WriteLine("We got connection: {0}", peer.EndPoint); // Show peer ip + NetDataWriter writer = new NetDataWriter(); // Create writer class + writer.Put("Hello client!"); // Put some string + peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability + }; + + while (!Console.KeyAvailable) + { + server.PollEvents(); + Thread.Sleep(15); + } + + server.Stop(); + } + + public void Client() + { + EventBasedNetListener listener = new EventBasedNetListener(); + NetManager client = new NetManager(listener); + client.Start(); + client.Connect("localhost" /* host ip or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */); + listener.NetworkReceiveEvent += (fromPeer, dataReader, deliveryMethod) => + { + Console.WriteLine("We got: {0}", dataReader.GetString(100 /* max length of string */)); + dataReader.Recycle(); + }; + + while (!Console.KeyAvailable) + { + client.PollEvents(); + Thread.Sleep(15); + } + + client.Stop(); + } + } +} + diff --git a/test/TCPTester.Client/App.config b/src/NSmartProxy.P2PClient46/App.config similarity index 59% rename from test/TCPTester.Client/App.config rename to src/NSmartProxy.P2PClient46/App.config index bae5d6d..731f6de 100644 --- a/test/TCPTester.Client/App.config +++ b/src/NSmartProxy.P2PClient46/App.config @@ -1,6 +1,6 @@ - + - + - + \ No newline at end of file diff --git a/test/TCPTester.Client/TCPTester.Client/TCPTester.Client.csproj b/src/NSmartProxy.P2PClient46/NSmartProxy.P2PClient46.csproj similarity index 80% rename from test/TCPTester.Client/TCPTester.Client/TCPTester.Client.csproj rename to src/NSmartProxy.P2PClient46/NSmartProxy.P2PClient46.csproj index 24684ab..8b8be80 100644 --- a/test/TCPTester.Client/TCPTester.Client/TCPTester.Client.csproj +++ b/src/NSmartProxy.P2PClient46/NSmartProxy.P2PClient46.csproj @@ -4,15 +4,14 @@ Debug AnyCPU - {7D314A13-EA88-462A-8048-6B488B6068AE} + {DA8FC4D6-FC4E-4761-B337-F2AD166B07B3} Exe - TCPTester.Client - TCPTester.Client - v4.6.1 + NSmartProxy.P2PClient46 + NSmartProxy.P2PClient46 + v4.6.2 512 true true - AnyCPU @@ -50,5 +49,11 @@ + + + {8604f141-1392-434c-af51-a1b73b214d98} + NSmartProxy.Infrastructure + + \ No newline at end of file diff --git a/src/NSmartProxy.P2PClient46/Program.cs b/src/NSmartProxy.P2PClient46/Program.cs new file mode 100644 index 0000000..212b9ad --- /dev/null +++ b/src/NSmartProxy.P2PClient46/Program.cs @@ -0,0 +1,109 @@ +using LiteNetLib; +using LiteNetLib.Utils; +using System; +using System.Text; +using System.Threading; + +namespace NSmartProxy.P2PClient46 +{ + class Program + { + private const int ServerPort = 12021; + private const string ConnectionKey = "test_key"; + private const byte hostByte = 0; + private const byte clientByte = 1; + static void Main(string[] args) + { + + EventBasedNetListener netListener = new EventBasedNetListener(); + EventBasedNatPunchListener netPunchListener = new EventBasedNatPunchListener(); + + NetManager client = new NetManager(netListener); + NetPeer peer = null; + netPunchListener.NatIntroductionRequest += NetPunchListener_NatIntroductionRequest1; + + //收到这个消息说明通道已经打通 + netPunchListener.NatIntroductionSuccess += (point, token) => + { + peer = client.Connect(point, ConnectionKey);//peer必须马上用 否则就没了? + Console.WriteLine("Success . Connecting to : {0}, connection created: {1}", point, peer != null); + //peer.Send(Encoding.UTF8.GetBytes("hello1"), DeliveryMethod.ReliableOrdered); + }; + + + netListener.NetworkReceiveEvent += NetListener_NetworkReceiveEvent; + netListener.PeerConnectedEvent += NetListener_PeerConnectedEvent; + + //netListener.PeerConnectedEvent += peer => + //{ + // Console.WriteLine("PeerConnected: " + peer.EndPoint.ToString()); + //}; + + //netListener.NetworkReceiveEvent += NetListener_NetworkReceiveEvent; + netListener.ConnectionRequestEvent += request => + { + request.AcceptIfKey(ConnectionKey);//关键方法 + Console.WriteLine("acceptifkey: connectionkey"); + }; + + netListener.PeerDisconnectedEvent += (p, disconnectInfo) => + { + Console.WriteLine("PeerDisconnected: " + disconnectInfo.Reason); + if (disconnectInfo.AdditionalData.AvailableBytes > 0) + { + Console.WriteLine("Disconnect data: " + disconnectInfo.AdditionalData.GetInt()); + } + }; + + client.NatPunchEnabled = true; + client.NatPunchModule.Init(netPunchListener); + //client.LocalPort; + client.Start(); + client.NatPunchModule.SendNatIntroduceRequest(NetUtils.MakeEndPoint("2017studio.imwork.net", ServerPort), "token2", clientByte); + while (true) + { + if (Console.KeyAvailable) + { + var key = Console.ReadKey(true).Key; + if (key == ConsoleKey.Escape) + { + break; + } + if (key == ConsoleKey.A) + { + //NetDataWriter ndw = new NetDataWriter(); + + //peer.Send() + //peer.Send(); + NetDataWriter ndw = new NetDataWriter(); + ndw.Put("hello,im" + netListener.GetHashCode()); + ndw.Put(new byte[2000]); + peer.Send(ndw, DeliveryMethod.ReliableOrdered); + //client.SendToAll(Encoding.ASCII.GetBytes("hello,im" + netListener.GetHashCode()), DeliveryMethod.ReliableOrdered); + } + } + client.NatPunchModule.PollEvents(); + client.PollEvents(); + Thread.Sleep(10); + } + Console.WriteLine("Hello World!"); + } + + private static void NetListener_PeerConnectedEvent(NetPeer peer) + { + int x = 0; + } + + private static void NetPunchListener_NatIntroductionRequest1(System.Net.IPEndPoint localEndPoint, System.Net.IPEndPoint remoteEndPoint, string token, byte hostOrClient) + { + Console.WriteLine("introrequest received:" + token + ",hostOrClient" + hostOrClient); + } + + + + private static void NetListener_NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) + { + Console.WriteLine("message received:" + reader.GetString()); + } + } +} diff --git a/test/TCPTester.Client/Properties/AssemblyInfo.cs b/src/NSmartProxy.P2PClient46/Properties/AssemblyInfo.cs similarity index 73% rename from test/TCPTester.Client/Properties/AssemblyInfo.cs rename to src/NSmartProxy.P2PClient46/Properties/AssemblyInfo.cs index bd1f0ff..ff276f8 100644 --- a/test/TCPTester.Client/Properties/AssemblyInfo.cs +++ b/src/NSmartProxy.P2PClient46/Properties/AssemblyInfo.cs @@ -5,12 +5,12 @@ // 有关程序集的一般信息由以下 // 控制。更改这些特性值可修改 // 与程序集关联的信息。 -[assembly: AssemblyTitle("TCPTester.Client")] +[assembly: AssemblyTitle("NSmartProxy.P2PClient46")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TCPTester.Client")] -[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyProduct("NSmartProxy.P2PClient46")] +[assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -20,7 +20,7 @@ [assembly: ComVisible(false)] // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("7d314a13-ea88-462a-8048-6b488b6068ae")] +[assembly: Guid("da8fc4d6-fc4e-4761-b337-f2ad166b07b3")] // 程序集的版本信息由下列四个值组成: // @@ -29,8 +29,8 @@ // 生成号 // 修订号 // -// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 -// 方法是按如下所示使用“*”: : +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/TCPTester.Client/TCPTester.Client/App.config b/src/NSmartProxy.P2PHost46/App.config similarity index 59% rename from test/TCPTester.Client/TCPTester.Client/App.config rename to src/NSmartProxy.P2PHost46/App.config index bae5d6d..731f6de 100644 --- a/test/TCPTester.Client/TCPTester.Client/App.config +++ b/src/NSmartProxy.P2PHost46/App.config @@ -1,6 +1,6 @@ - + - + - + \ No newline at end of file diff --git a/test/TCPTester.Server/TCPTester.Server/TCPTester.Server.csproj b/src/NSmartProxy.P2PHost46/NSmartProxy.P2PHost46.csproj similarity index 80% rename from test/TCPTester.Server/TCPTester.Server/TCPTester.Server.csproj rename to src/NSmartProxy.P2PHost46/NSmartProxy.P2PHost46.csproj index 7adeffc..6fbf234 100644 --- a/test/TCPTester.Server/TCPTester.Server/TCPTester.Server.csproj +++ b/src/NSmartProxy.P2PHost46/NSmartProxy.P2PHost46.csproj @@ -4,15 +4,14 @@ Debug AnyCPU - {0867C939-8FA0-4E21-A9E6-D040D3B043DC} + {422507C5-3C57-4526-A028-8C2E5B213813} Exe - TCPTester.Server - TCPTester.Server - v4.6.1 + NSmartProxy.P2PClient46 + NSmartProxy.P2PClient46 + v4.6.2 512 true true - AnyCPU @@ -50,5 +49,11 @@ + + + {8604f141-1392-434c-af51-a1b73b214d98} + NSmartProxy.Infrastructure + + \ No newline at end of file diff --git a/src/NSmartProxy.P2PHost46/Program.cs b/src/NSmartProxy.P2PHost46/Program.cs new file mode 100644 index 0000000..aff21c0 --- /dev/null +++ b/src/NSmartProxy.P2PHost46/Program.cs @@ -0,0 +1,109 @@ +using LiteNetLib; +using LiteNetLib.Utils; +using System; +using System.Text; +using System.Threading; + +namespace NSmartProxy.P2PClient46 +{ + class Program + { + private const int ServerPort = 12021; + private const string ConnectionKey = "test_key"; + private const byte hostByte = 0; + private const byte clientByte = 1; + static void Main(string[] args) + { + + EventBasedNetListener netListener = new EventBasedNetListener(); + EventBasedNatPunchListener netPunchListener = new EventBasedNatPunchListener(); + + NetManager client = new NetManager(netListener); + NetPeer peer = null; + netPunchListener.NatIntroductionRequest += NetPunchListener_NatIntroductionRequest1; + + //收到这个消息说明通道已经打通 + netPunchListener.NatIntroductionSuccess += (point, token) => + { + peer = client.Connect(point, ConnectionKey);//peer必须马上用 否则就没了? + Console.WriteLine("Success . Connecting to : {0}, connection created: {1}", point, peer != null); + //peer.Send(Encoding.UTF8.GetBytes("hello1"), DeliveryMethod.ReliableOrdered); + }; + + + netListener.NetworkReceiveEvent += NetListener_NetworkReceiveEvent; + netListener.PeerConnectedEvent += NetListener_PeerConnectedEvent; + + //netListener.PeerConnectedEvent += peer => + //{ + // Console.WriteLine("PeerConnected: " + peer.EndPoint.ToString()); + //}; + + //netListener.NetworkReceiveEvent += NetListener_NetworkReceiveEvent; + netListener.ConnectionRequestEvent += request => + { + request.AcceptIfKey(ConnectionKey);//关键方法 + Console.WriteLine("acceptifkey: connectionkey"); + }; + + netListener.PeerDisconnectedEvent += (p, disconnectInfo) => + { + Console.WriteLine("PeerDisconnected: " + disconnectInfo.Reason); + if (disconnectInfo.AdditionalData.AvailableBytes > 0) + { + Console.WriteLine("Disconnect data: " + disconnectInfo.AdditionalData.GetInt()); + } + }; + + client.NatPunchEnabled = true; + client.NatPunchModule.Init(netPunchListener); + //client.LocalPort; + client.Start(); + client.NatPunchModule.SendNatIntroduceRequest(NetUtils.MakeEndPoint("2017studio.imwork.net", ServerPort), "token2", hostByte); + while (true) + { + if (Console.KeyAvailable) + { + var key = Console.ReadKey(true).Key; + if (key == ConsoleKey.Escape) + { + break; + } + if (key == ConsoleKey.A) + { + //NetDataWriter ndw = new NetDataWriter(); + + //peer.Send() + //peer.Send(); + NetDataWriter ndw = new NetDataWriter(); + ndw.Put("hello,im" + netListener.GetHashCode()); + ndw.Put(new byte[2000]); + peer.Send(ndw, DeliveryMethod.ReliableOrdered); + //client.SendToAll(Encoding.ASCII.GetBytes("hello,im" + netListener.GetHashCode()), DeliveryMethod.ReliableOrdered); + } + } + client.NatPunchModule.PollEvents(); + client.PollEvents(); + Thread.Sleep(10); + } + Console.WriteLine("Hello World!"); + } + + private static void NetListener_PeerConnectedEvent(NetPeer peer) + { + int x = 0; + } + + private static void NetPunchListener_NatIntroductionRequest1(System.Net.IPEndPoint localEndPoint, System.Net.IPEndPoint remoteEndPoint, string token, byte hostOrClient) + { + Console.WriteLine("introrequest received:" + token + ",hostOrClient" + hostOrClient); + } + + + + private static void NetListener_NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) + { + Console.WriteLine("message received:" + reader.GetString()); + } + } +} diff --git a/test/TCPTester.Server/Properties/AssemblyInfo.cs b/src/NSmartProxy.P2PHost46/Properties/AssemblyInfo.cs similarity index 73% rename from test/TCPTester.Server/Properties/AssemblyInfo.cs rename to src/NSmartProxy.P2PHost46/Properties/AssemblyInfo.cs index c272ba5..ff276f8 100644 --- a/test/TCPTester.Server/Properties/AssemblyInfo.cs +++ b/src/NSmartProxy.P2PHost46/Properties/AssemblyInfo.cs @@ -5,12 +5,12 @@ // 有关程序集的一般信息由以下 // 控制。更改这些特性值可修改 // 与程序集关联的信息。 -[assembly: AssemblyTitle("TCPTester.Server")] +[assembly: AssemblyTitle("NSmartProxy.P2PClient46")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TCPTester.Server")] -[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyProduct("NSmartProxy.P2PClient46")] +[assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -20,7 +20,7 @@ [assembly: ComVisible(false)] // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("0867c939-8fa0-4e21-a9e6-d040d3b043dc")] +[assembly: Guid("da8fc4d6-fc4e-4761-b337-f2ad166b07b3")] // 程序集的版本信息由下列四个值组成: // @@ -29,8 +29,8 @@ // 生成号 // 修订号 // -// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 -// 方法是按如下所示使用“*”: : +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/NSmartProxy.ServerHost/Dockerfile b/src/NSmartProxy.ServerHost/Dockerfile index 2bac347..9884c1d 100644 --- a/src/NSmartProxy.ServerHost/Dockerfile +++ b/src/NSmartProxy.ServerHost/Dockerfile @@ -16,7 +16,7 @@ #ENTRYPOINT ["dotnet", "NSmartProxy.ServerHost.dll"] #need combile 1st. -FROM microsoft/dotnet:2.2-aspnetcore-runtime AS runtime +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS runtime WORKDIR /app COPY / /app/ EXPOSE 12300-22300 diff --git a/src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj b/src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj index 7ccf543..2ec37ad 100644 --- a/src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj +++ b/src/NSmartProxy.ServerHost/NSmartProxy.ServerHost.csproj @@ -2,8 +2,8 @@ Exe - netcoreapp2.2 - false + net6.0 + true @@ -11,12 +11,15 @@ - + + PreserveNewest + - - + + + @@ -24,8 +27,10 @@ - - PreserveNewest + + Always + Always + true PreserveNewest @@ -33,6 +38,7 @@ 7.1 + ../../test/build/nspserver diff --git a/src/NSmartProxy.ServerHost/Program.cs b/src/NSmartProxy.ServerHost/Program.cs new file mode 100644 index 0000000..eb08267 --- /dev/null +++ b/src/NSmartProxy.ServerHost/Program.cs @@ -0,0 +1,51 @@ +using NSmartProxy.Shared; +using PeterKottas.DotNetCore.WindowsService; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NSmartProxy.ServerHost +{ + public class Program + { + static void Main() + { + //wait + ServiceRunner.Run(config => + { + var name = Global.NSPServerServiceName; + config.SetDisplayName(Global.NSPServerServiceName); + config.SetName(Global.NSPServerDisplayName); + config.SetDescription(NSPVersion.NSmartProxyServerName); + + config.Service(serviceConfig => + { + serviceConfig.ServiceFactory((extraArguments, controller) => + { + return new ServerHost(); + }); + + serviceConfig.OnStart((service, extraParams) => + { + Console.WriteLine("Service {0} started", name); + Task.Run(() => service.Start()); + }); + + serviceConfig.OnStop(service => + { + Console.WriteLine("Service {0} stopped", name); + Task.Run(() => service.Stop()); + }); + + serviceConfig.OnError(e => + { + Console.WriteLine("Service {0} errored with exception : {1}", name, e.Message); + }); + }); + + + }); + } + } +} diff --git a/src/NSmartProxy.ServerHost/ServerHost.cs b/src/NSmartProxy.ServerHost/ServerHost.cs index a92641f..ad76550 100644 --- a/src/NSmartProxy.ServerHost/ServerHost.cs +++ b/src/NSmartProxy.ServerHost/ServerHost.cs @@ -11,13 +11,16 @@ using NSmartProxy.Data.Config; using NSmartProxy.Infrastructure; using NSmartProxy.Shared; +using PeterKottas.DotNetCore.WindowsService.Interfaces; namespace NSmartProxy.ServerHost { - class ServerHost + public class ServerHost : IMicroService { private static Mutex mutex = new Mutex(true, "{8639B0AD-A27C-4F15-B3D9-08035D0FC6D6}"); + private static ILog Logger; + #region logger public class Log4netLogger : INSmartLogger { @@ -40,65 +43,57 @@ public void Info(object message) } #endregion - public static IConfigurationRoot Configuration { get; set; } - public static ILog Logger; + public IConfigurationRoot Configuration { get; set; } + + private static readonly string ConfigFilePath = ConfigHelper.AppSettingFullPath; - public const string CONFIG_FILE_PATH = "./appsettings.json"; - static void Main(string[] args) + public void Start() { if (!mutex.WaitOne(3, false)) { + //如果启动多个实例,则警告 string msg = "Another instance of the program is running.It may cause fatal error."; - //Logger.Error(msg, new Exception(msg)); Console.ForegroundColor = ConsoleColor.DarkRed; - Console.Write(msg); - //return; - //Console.ForegroundColor = default(ConsoleColor); + Console.WriteLine(msg); } Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Initializing.."); //log + InitLogConfig(); + StartNSPServer(); + } + + private void InitLogConfig() + { var loggerRepository = LogManager.CreateRepository("NSmartServerRepository"); XmlConfigurator.ConfigureAndWatch(loggerRepository, new FileInfo("log4net.config")); Logger = LogManager.GetLogger(loggerRepository.Name, "NSmartServer"); if (!loggerRepository.Configured) throw new Exception("log config failed."); - Logger.Debug($"*** {Global.NSmartProxyServerName} ***"); + Logger.Debug($"*** {NSPVersion.NSmartProxyServerName} ***"); var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(CONFIG_FILE_PATH); + .AddJsonFile(ConfigFilePath); Configuration = builder.Build(); - StartServer(); } - private static void StartServer() + private static void StartNSPServer() { - //try - //{ - // Server.ClientServicePort = int.Parse(Configuration.GetSection("ClientServicePort").Value); - // Server.ConfigServicePort = int.Parse(Configuration.GetSection("ConfigServicePort").Value); - //} - //catch (Exception ex) - //{ - // Logger.Debug("配置文件读取失败:" + ex.ToString()); - // return; - //} - NSPServerConfig serverConfig = null; //初始化配置 - if (!File.Exists(CONFIG_FILE_PATH)) + if (!File.Exists(ConfigFilePath)) { serverConfig = new NSPServerConfig(); - serverConfig.SaveChanges(CONFIG_FILE_PATH); + serverConfig.SaveChanges(ConfigFilePath); } else { - serverConfig = ConfigHelper.ReadAllConfig(CONFIG_FILE_PATH); + serverConfig = ConfigHelper.ReadAllConfig(ConfigFilePath); } - + Server srv = new Server(new Log4netLogger()); @@ -110,10 +105,10 @@ private static void StartServer() try { watch.Start(); - srv//.SetWebPort(int.Parse(Configuration.GetSection("WebAPIPort").Value)) + srv .SetConfiguration(serverConfig) - .SetAnonymousLogin(true) - .SetServerConfigPath(CONFIG_FILE_PATH) + //.SetAnonymousLogin(false) 从配置文件获取 + .SetServerConfigPath(ConfigFilePath) .Start() .Wait(); } @@ -151,5 +146,12 @@ private static void StartServer() // ignored } } + + public void Stop() + { + // + Console.WriteLine(NSPVersion.NSmartProxyClientName + " STOPPED."); + Environment.Exit(0); + } } } diff --git a/src/NSmartProxy.ServerHost/appsettings.json b/src/NSmartProxy.ServerHost/appsettings.json index 3f38005..023d03f 100644 --- a/src/NSmartProxy.ServerHost/appsettings.json +++ b/src/NSmartProxy.ServerHost/appsettings.json @@ -1,5 +1,7 @@ { - "ClientServicePort": 19974, //反向连接端口 - "ConfigServicePort": 12308, //配置服务端口 - "WebAPIPort": 12309 //API服务端口 + "ReversePort": 7842, //反向连接端口 + "ConfigPort": 7841, //配置服务端口 + "WebAPIPort": 12309, //API服务端口 + "ReversePort_Out": 0, //对外端口,如果服务端存在端口转换需要配置此项,为0或者不配默认用内网端口 + "ConfigPort_Out": 0 //对外端口,如果服务端存在端口转换需要配置此项,为0或者不配默认用内网端口 } \ No newline at end of file diff --git a/src/NSmartProxy.sln b/src/NSmartProxy.sln index 2f2334a..3e506e5 100644 --- a/src/NSmartProxy.sln +++ b/src/NSmartProxy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.106 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxy", "NSmartProxy\NSmartProxy.csproj", "{AAB4283A-6A0F-4258-A86F-CDABA316556B}" EndProject @@ -15,32 +15,36 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxy.ClientRouter", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxy.Infrastructure", "NSmartProxy.Infrastructure\NSmartProxy.Infrastructure.csproj", "{8604F141-1392-434C-AF51-A1B73B214D98}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxyClient", "NSmartProxyClient\NSmartProxyClient.csproj", "{2654C6B4-E3F0-4F66-9CB3-F267790AC5BE}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSmartProxyWinform", "NSmartProxyWinform\NSmartProxyWinform.csproj", "{B55F3DF3-6657-4204-AEEB-B2360FC5FAB1}" EndProject -Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "HttpServerStaticFiles", "NSmartProxy\Extension\HttpServerStaticFiles\", "{0AD8E5B3-E7DE-456D-A5BA-5CB937DAC10D}" +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Web", "NSmartProxy\Web\", "{0AD8E5B3-E7DE-456D-A5BA-5CB937DAC10D}" ProjectSection(WebsiteProperties) = preProject - TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.8" Debug.AspNetCompiler.VirtualPath = "/localhost_5671" - Debug.AspNetCompiler.PhysicalPath = "NSmartProxy\Extension\HttpServerStaticFiles\" + Debug.AspNetCompiler.PhysicalPath = "NSmartProxy\Web\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_5671\" Debug.AspNetCompiler.Updateable = "true" Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.FixedNames = "false" Debug.AspNetCompiler.Debug = "True" Release.AspNetCompiler.VirtualPath = "/localhost_5671" - Release.AspNetCompiler.PhysicalPath = "NSmartProxy\Extension\HttpServerStaticFiles\" + Release.AspNetCompiler.PhysicalPath = "NSmartProxy\Web\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_5671\" Release.AspNetCompiler.Updateable = "true" Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" VWDPort = "5671" - SlnRelativePath = "NSmartProxy\Extension\HttpServerStaticFiles\" + SlnRelativePath = "NSmartProxy\Web\" EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSmartProxy.NUnitTest", "NSmartProxy.NUnitTest\NSmartProxy.NUnitTest.csproj", "{DDF2C0DE-FBFD-4AC2-BD00-44D96D9833B7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSmartProxyWinService", "NSmartProxyWinService\NSmartProxyWinService.csproj", "{D5488369-60D1-47E5-880D-8D5F49A0D777}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxyClient", "NSmartProxyClient\NSmartProxyClient.csproj", "{9ADE2698-A6B3-46EE-AE17-E553F168FF04}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestBed", "TestBed", "{9C608D62-296C-4CB3-A2F3-599118DACF97}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSmartProxy.ConsoleTest", "TestBed\NSmartProxy.ConsoleTest\NSmartProxy.ConsoleTest.csproj", "{DB615C5A-8288-4A4F-9025-7198D5F8E0AA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -68,10 +72,6 @@ Global {8604F141-1392-434C-AF51-A1B73B214D98}.Debug|Any CPU.Build.0 = Debug|Any CPU {8604F141-1392-434C-AF51-A1B73B214D98}.Release|Any CPU.ActiveCfg = Release|Any CPU {8604F141-1392-434C-AF51-A1B73B214D98}.Release|Any CPU.Build.0 = Release|Any CPU - {2654C6B4-E3F0-4F66-9CB3-F267790AC5BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2654C6B4-E3F0-4F66-9CB3-F267790AC5BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2654C6B4-E3F0-4F66-9CB3-F267790AC5BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2654C6B4-E3F0-4F66-9CB3-F267790AC5BE}.Release|Any CPU.Build.0 = Release|Any CPU {B55F3DF3-6657-4204-AEEB-B2360FC5FAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B55F3DF3-6657-4204-AEEB-B2360FC5FAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {B55F3DF3-6657-4204-AEEB-B2360FC5FAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -80,20 +80,33 @@ Global {0AD8E5B3-E7DE-456D-A5BA-5CB937DAC10D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0AD8E5B3-E7DE-456D-A5BA-5CB937DAC10D}.Release|Any CPU.ActiveCfg = Debug|Any CPU {0AD8E5B3-E7DE-456D-A5BA-5CB937DAC10D}.Release|Any CPU.Build.0 = Debug|Any CPU - {DDF2C0DE-FBFD-4AC2-BD00-44D96D9833B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDF2C0DE-FBFD-4AC2-BD00-44D96D9833B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDF2C0DE-FBFD-4AC2-BD00-44D96D9833B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDF2C0DE-FBFD-4AC2-BD00-44D96D9833B7}.Release|Any CPU.Build.0 = Release|Any CPU + {D5488369-60D1-47E5-880D-8D5F49A0D777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5488369-60D1-47E5-880D-8D5F49A0D777}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5488369-60D1-47E5-880D-8D5F49A0D777}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5488369-60D1-47E5-880D-8D5F49A0D777}.Release|Any CPU.Build.0 = Release|Any CPU + {9ADE2698-A6B3-46EE-AE17-E553F168FF04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9ADE2698-A6B3-46EE-AE17-E553F168FF04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9ADE2698-A6B3-46EE-AE17-E553F168FF04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9ADE2698-A6B3-46EE-AE17-E553F168FF04}.Release|Any CPU.Build.0 = Release|Any CPU + {DB615C5A-8288-4A4F-9025-7198D5F8E0AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB615C5A-8288-4A4F-9025-7198D5F8E0AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB615C5A-8288-4A4F-9025-7198D5F8E0AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB615C5A-8288-4A4F-9025-7198D5F8E0AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {094B0334-FEDB-4867-9960-03382408BFBA} = {30378AB8-F008-4AB6-8DB7-71B7C706C604} - {2654C6B4-E3F0-4F66-9CB3-F267790AC5BE} = {30378AB8-F008-4AB6-8DB7-71B7C706C604} {B55F3DF3-6657-4204-AEEB-B2360FC5FAB1} = {30378AB8-F008-4AB6-8DB7-71B7C706C604} + {D5488369-60D1-47E5-880D-8D5F49A0D777} = {30378AB8-F008-4AB6-8DB7-71B7C706C604} + {9ADE2698-A6B3-46EE-AE17-E553F168FF04} = {30378AB8-F008-4AB6-8DB7-71B7C706C604} + {DB615C5A-8288-4A4F-9025-7198D5F8E0AA} = {9C608D62-296C-4CB3-A2F3-599118DACF97} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CBECE17A-7F2B-4821-843B-65DCE2A768C2} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/src/NSmartProxy/Authorize/NSPServerContext.cs b/src/NSmartProxy/Authorize/NSPServerContext.cs index 9911716..6090e85 100644 --- a/src/NSmartProxy/Authorize/NSPServerContext.cs +++ b/src/NSmartProxy/Authorize/NSPServerContext.cs @@ -1,39 +1,45 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography.X509Certificates; using System.Text; using NSmartProxy.Data; using NSmartProxy.Data.Config; +using NSmartProxy.Infrastructure; +using NSmartProxy.Infrastructure.Interfaces; namespace NSmartProxy.Authorize { /// /// 存放服务端的状态,以供其他组件共享 /// - public class NSPServerContext + public class NSPServerContext : IServerContext { public NSPClientCollection Clients; - public Dictionary PortAppMap;//端口和app的映射关系 + public Dictionary PortAppMap;//“地址:端口”和app的映射关系 + public Dictionary UDPPortAppMap;//UDP “地址:端口”和app的映射关系 public NSPServerConfig ServerConfig; public HashSet TokenCaches; //服务端会话池,登录后的会话都在这里,每天需要做定时清理 public long TotalReceivedBytes; //TODO 统计进出数据 下行 public long TotalSentBytes;//上行 public long ConnectCount;//连接次数 public long ClientConnectCount; //客户端连接次数 - - private bool supportAnonymousLogin = true; + public Dictionary PortCertMap;//host->证书字典 public NSPServerContext() { TokenCaches = new HashSet(); Clients = new NSPClientCollection(); - PortAppMap = new Dictionary(); + PortAppMap = new Dictionary(); + UDPPortAppMap = new Dictionary(); + PortCertMap = new Dictionary(); //ServerConfig = new NSPServerConfig(); } /// /// 支持客户端匿名登录 - /// - public bool SupportAnonymousLogin { get => supportAnonymousLogin; set => supportAnonymousLogin = value; } + /// + public bool SupportAnonymousLogin { get => ServerConfig.supportAnonymousLogin; set => ServerConfig.supportAnonymousLogin = value; } public string ServerConfigPath { get; set; } @@ -63,32 +69,75 @@ public void AddPortMap() //使得被用户绑定的端口无法被分配到 } } + private object locker = new object(); /// /// 上下文中删除特定客户端 /// /// - public void CloseAllSourceByClient(int clientId, bool addToBanlist = false) + public void CloseAllSourceByClient(int clientId, bool addToBanlist = false, bool isForceClose = false) { if (Clients.ContainsKey(clientId)) { NSPClient client = Clients[clientId]; string msg = ""; - foreach (var appKV in client.AppMap) + lock (locker) { - int port = appKV.Value.ConsumePort; - //1.关闭,并移除AppMap中的App - PortAppMap[port].Close(); - PortAppMap.Remove(port); - msg += appKV.Value.ConsumePort + " "; - //2.移除端口占用 - NetworkUtil.ReleasePort(port); + foreach (var appKV in client.AppMap) + { + int port = appKV.Value.ConsumePort; + var appMap = appKV.Value.AppProtocol == Protocol.UDP ? UDPPortAppMap : PortAppMap; + //1.关闭,并移除AppMap中的App + if (!appMap.ContainsKey(port)) + { + Server.Logger.Debug($"clientid:{clientId}不包含port:{port}"); + } + else + { + //TODO 3 关闭时会造成连锁反应 + //TODO 3 并且当nspgroup里的元素全都被关闭时,才remove掉这个节点 + //PortAppMap[port] + //PortAppMap[port].CloseByHost(); + var nspAppGroup = appMap[port]; + foreach (var (host, app) in nspAppGroup) + { + if (app.ClientId == clientId) + { + app.Close(isForceClose); + } + } + + if (nspAppGroup.IsAllClosed()) + { + Server.Logger.Info($"端口{port}所有app退出,侦听终止"); + if (nspAppGroup.Listener != null) + { + //如果port内所有的app全都移除,才关闭listener + nspAppGroup.Listener.Server.NoDelay = true; + nspAppGroup.Listener.Server.Close(); + nspAppGroup.Listener.Stop(); + } + + if (nspAppGroup.UdpClient != null) + { + nspAppGroup.UdpClient.Close(); + } + + appMap.Remove(port); + } + } + + + msg += appKV.Value.ConsumePort + " "; + //2.移除端口占用 + NetworkUtil.ReleasePort(port); + } } //3.移除client try { - int closedClients = Clients.UnRegisterClient(client.ClientID); - Server.Logger.Info(msg + $"已移除, {client.ClientID} 中的 {closedClients}个传输已终止。"); + Clients.UnRegisterClient(client.ClientID); + Server.Logger.Info(msg + $"已移除, {client.ClientID} 中的 传输已终止。"); } catch (Exception ex) { @@ -100,5 +149,36 @@ public void CloseAllSourceByClient(int clientId, bool addToBanlist = false) Server.Logger.Debug($"无此id: {clientId},可能已关闭过"); } } + + /// + /// 通过配置初始化证书到证书缓存中 + /// + public void InitCertificates() + { + foreach (var (port, path) in ServerConfig.CABoundConfig) + { + if (File.Exists(path)) + { + //从文件里加载证书 + PortCertMap[port] = X509Certificate2.CreateFromCertFile(path); + } + else + { + Server.Logger.Debug($"未加载位于{port}的证书。"); + } + } + + } + + public void SaveConfigChanges() + { + if (string.IsNullOrEmpty(ServerConfigPath)) + { + throw new Exception("配置路径ServerConfigPath为空。"); + } + + ServerConfig.SaveChanges(ServerConfigPath); + } + } } diff --git a/src/NSmartProxy/ClientConnectionManager.cs b/src/NSmartProxy/ClientConnectionManager.cs index c41ab74..bcdccae 100644 --- a/src/NSmartProxy/ClientConnectionManager.cs +++ b/src/NSmartProxy/ClientConnectionManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel.Design; using System.Net; using System.Net.Sockets; using System.Text; @@ -25,8 +26,8 @@ public class ClientConnectionManager /// /// 当app增加时触发 /// - public event EventHandler AppTcpClientMapReverseConnected = delegate { }; - public event EventHandler AppTcpClientMapConfigConnected = delegate { }; + // public event EventHandler AppTcpClientMapReverseConnected = delegate { }; + public event EventHandler AppTcpClientMapConfigApplied = delegate { }; private NSPServerContext ServerContext; @@ -43,8 +44,8 @@ private ClientConnectionManager() public async Task ListenServiceClient(IDbOperator dbOp) { //侦听,并且构造连接池 - Server.Logger.Debug("Listening client on port " + ServerContext.ServerConfig.ClientServicePort + "..."); - TcpListener listener = new TcpListener(IPAddress.Any, ServerContext.ServerConfig.ClientServicePort); + Server.Logger.Debug("Listening client on port " + ServerContext.ServerConfig.ReversePort + "..."); + TcpListener listener = new TcpListener(IPAddress.Any, ServerContext.ServerConfig.ReversePort); listener.Start(1000); while (true) { @@ -53,7 +54,7 @@ public async Task ListenServiceClient(IDbOperator dbOp) incomeClient.Client.Client.LocalEndPoint.ToString() + "-" + incomeClient.Client.Client.RemoteEndPoint.ToString()); incomeClient.Client.SetKeepAlive(out _); - ProcessReverseRequest(incomeClient); + _ = ProcessReverseRequest(incomeClient); } } @@ -80,6 +81,8 @@ private async Task ProcessReverseRequest(SecurityTcpClient incomeClient) try { var result = await incomeClient.AuthorizeAsync(); + if (result == null) return;//主动关闭 + if (!result.IsSuccess) { Server.Logger.Debug("SecurityTcpClient校验失败:" + incomeClient.ErrorMessage); @@ -102,7 +105,6 @@ private async Task ProcessReverseRequest(SecurityTcpClient incomeClient) } - var clientIdAppId = GetAppFromBytes(bytes); Server.Logger.Debug("已获取到消息ClientID:" + clientIdAppId.ClientID + "AppID:" + clientIdAppId.AppID @@ -127,28 +129,67 @@ private async Task ProcessReverseRequest(SecurityTcpClient incomeClient) private static readonly ClientConnectionManager Instance = new Lazy(() => new ClientConnectionManager()).Value; + public TcpClient GetClientForUdp(int consumerPort, string host = null) + { + NSPApp nspApp = null; + nspApp = ServerContext.UDPPortAppMap[consumerPort].ActivateApp; + + TcpClient client = nspApp.TcpClientBlocks.Peek(); + if (client == null) + { + throw new TimeoutException($"UDP获取{consumerPort}失败"); + } + return client; + } - public async Task GetClient(int consumerPort) + /// + /// 从当前app的连接池取出一个socket + /// + /// + /// + /// 仅仅获取这个client 而不消费它(开启新连接) + /// + public async Task GetClientForTcp(int consumerPort, string host = null) { - var clientId = ServerContext.PortAppMap[consumerPort].ClientId; - var appId = ServerContext.PortAppMap[consumerPort].AppId; + NSPApp nspApp = null; + if (host == null || string.IsNullOrEmpty(host.Trim())) //host为空则随便匹配一个 + { nspApp = ServerContext.PortAppMap[consumerPort].ActivateApp; } + else + { + if (!ServerContext.PortAppMap[consumerPort].TryGetValue(host, out nspApp)) + { + //throw new KeyNotFoundException($"无法找到{consumerPort}的host:{host}"); + Server.Logger.Debug($"无法找到{consumerPort}的host:{host}"); + nspApp = ServerContext.PortAppMap[consumerPort].ActivateApp; + } + } + if (nspApp == null) throw new KeyNotFoundException($"无法找到{consumerPort}下的任何一个客户端app"); //TODO ***需要处理服务端长时间不来请求的情况(无法建立隧道) - TcpClient client = await ServerContext.Clients[clientId].AppMap[appId].PopClientAsync(); - if (client == null) return null; - ServerContext.PortAppMap[consumerPort].ReverseClients.Add(client); + //TODO 2这里的弹出比较麻烦了 + TcpClient client = await nspApp.PopClientAsync(); + if (client == null) + { + throw new TimeoutException($"弹出{consumerPort}超时"); + } + + //TODO 2 反向链接还写在这里?? + //ServerContext.PortAppMap[consumerPort].ActivateApp.ReverseClients.Add(client); + nspApp.ReverseClients.Add(client); return client; } - //通过客户端的id请求,分配好服务端端口和appid交给客户端 - //arrange ConfigId from top 4 bytes which received from client. - //response: - // 2 1 1 1 1 ...N - // clientid appid port appid2 port2 - //request: - // 2 2 - // clientid count - // methodType value = 0 + /// + /// 分配端口方法 + /// arrange ConfigId from top 4 bytes which received from client. + /// response: + /// 2 1 1 1 1 ...N + /// clientid appid port appid2 port2 + /// request: + /// 2 2 + /// clientid count + /// methodType value = 0 + /// public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, int highPriorityClientId) { // byte[] arrangedBytes = new byte[256]; @@ -164,7 +205,6 @@ public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, clientId = StringUtil.DoubleBytesToInt(appRequestBytes[0], appRequestBytes[1]); } - //2.分配clientid //TODO 这一段可能不会再用到了 int appCount = (int)appRequestBytes[2]; @@ -177,7 +217,7 @@ public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, for (int i = 0; i < 10000; i++) { _rand.NextBytes(tempClientIdBytes); - int tempClientId = (tempClientIdBytes[0] << 8) + tempClientIdBytes[1]; + int tempClientId = StringUtil.DoubleBytesToInt(tempClientIdBytes); if (!ServerContext.Clients.ContainsKey(tempClientId)) { @@ -196,33 +236,91 @@ public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, //注册客户端 ServerContext.Clients.RegisterNewClient(clientModel.ClientId); + //port proto option(iscompress) host description + //2 1 1 1024 96 + int oneEndpointLength = 2 + 1 + 1 + 1024 + 96; lock (_lockObject2) { //注册app clientModel.AppList = new List(appCount); for (int i = 0; i < appCount; i++) { - int startPort = StringUtil.DoubleBytesToInt(consumerPortBytes[2 * i], consumerPortBytes[2 * i + 1]); + int offset = oneEndpointLength * i; + int startPort = StringUtil.DoubleBytesToInt(consumerPortBytes[offset], consumerPortBytes[offset + 1]); int arrangedAppid = ServerContext.Clients[clientId].RegisterNewApp(); + Protocol protocol = (Protocol)consumerPortBytes[offset + 2]; + bool isCompress = consumerPortBytes[offset + 3] == 1 ? true : false; + string host = Encoding.ASCII.GetString(consumerPortBytes, offset + 4, 1024).TrimEnd('\0'); + string description = Encoding.UTF8.GetString(consumerPortBytes, offset + 4 + 1024, 96).TrimEnd('\0'); //查找port的起始端口如果未指定,则设置为20000 - if (startPort == 0) startPort = 20000; + if (startPort == 0) startPort = Global.StartArrangedPort; int port = 0; - //TODO QQQ 如果端口是指定的并且是绑定的,则直接使用该端口即可 + //如果端口是指定的并且是绑定的,不加任何检测 + bool hasListened = false; if (IsBoundedByUser(clientId, startPort)) { port = startPort; } else { - port = NetworkUtil.FindOneAvailableTCPPort(startPort); + if (protocol == Protocol.TCP) + { + int relocatedPort = NetworkUtil.FindOneAvailableTCPPort(startPort); + port = relocatedPort; + } + else if (protocol == Protocol.UDP) + { + int relocatedPort = NetworkUtil.FindOneAvailableUDPPort(startPort); + port = relocatedPort; + } + else if (protocol == Protocol.HTTP) + {//因为端口复用,允许公用端口 + int relocatedPort = NetworkUtil.FindOneAvailableTCPPort(startPort); + //兼容http侦听端口公用 + if (port != relocatedPort) + { + //http协议下如果portappmap已经有值,说明已经发起过侦听,接下来不必再侦听 + if (ServerContext.PortAppMap.ContainsKey(startPort)) + { + port = startPort; + hasListened = true; + } + else + { + port = relocatedPort; + } + } + } + } NSPApp app = ServerContext.Clients[clientId].AppMap[arrangedAppid]; app.ClientId = clientId; app.AppId = arrangedAppid; app.ConsumePort = port; + app.AppProtocol = protocol; + app.Host = host; + app.Description = description; app.Tunnels = new List(); app.ReverseClients = new List(); - ServerContext.PortAppMap[port] = app; + app.IsCompress = isCompress; + + if (protocol == Protocol.UDP) + { + if (!ServerContext.UDPPortAppMap.ContainsKey(port)) + { + ServerContext.UDPPortAppMap[port] = new NSPAppGroup(); + } + ServerContext.UDPPortAppMap[port][host] = app; + } + else + { + //TODO 设置app的host和protocoltype(TCP Only) + if (!ServerContext.PortAppMap.ContainsKey(port)) + { + ServerContext.PortAppMap[port] = new NSPAppGroup(); + } + ServerContext.PortAppMap[port][host] = app; + } clientModel.AppList.Add(new App { @@ -232,7 +330,10 @@ public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, Logger.Info(port); //配置时触发 - AppTcpClientMapConfigConnected(this, new AppChangedEventArgs() { App = app }); + if (!hasListened) + { + AppTcpClientMapConfigApplied(this, new AppChangedEventArgs() { App = app });//触发listener侦听 + } } Logger.Debug(" <=端口已分配。"); } @@ -242,8 +343,7 @@ public byte[] ArrangeConfigIds(byte[] appRequestBytes, byte[] consumerPortBytes, private bool IsBoundedByUser(int clientId, int port) { var boundHash = ServerContext.ServerConfig.BoundConfig.UserPortBounds; - UserPortBound userBound; - if (boundHash.TryGetValue(clientId.ToString(), out userBound)) + if (boundHash.TryGetValue(clientId.ToString(), out UserPortBound userBound)) { return userBound.Bound.Contains(port); } diff --git a/src/NSmartProxy/Database/LiteDbOperator.cs b/src/NSmartProxy/Database/LiteDbOperator.cs new file mode 100644 index 0000000..0c797c7 --- /dev/null +++ b/src/NSmartProxy/Database/LiteDbOperator.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using LiteDB; + +namespace NSmartProxy.Database +{ + public class LiteDbOperator : IDbOperator + { + public const string SUPER_VARIABLE_INDEX_ID = "$index_id$"; + + private LiteDatabase liteDb; + private string filePath; + private bool isClosed = true;//默认未开启状态 + + private LiteCollection liteCollection; + + ConcurrentDictionary keyCache = new ConcurrentDictionary();//缓存机制,防止重复大量查数据库 + //rvate + + public LiteDbOperator(String file) + { + // if (indexFile == null) indexFile = "idx_" + file; + filePath = file; + Open(); + } + + public void Dispose() + { + liteDb.Dispose(); + } + + public IDbOperator Open() + { + if (isClosed) + { + liteDb = new LiteDatabase(filePath); + liteCollection = (LiteCollection)liteDb.GetCollection("users"); + } + + return this; + } + + public void Insert(long key, string value) + { + liteCollection.Insert(new KV( + key.ToString(), + value.Replace(SUPER_VARIABLE_INDEX_ID, GetAvailableKey(65535).ToString())) + ); + } + + public void Insert(string key, string value) + { + liteCollection.Insert(new KV( + key, + value.Replace(SUPER_VARIABLE_INDEX_ID, GetAvailableKey(65535).ToString())) + ); + } + + //TODO 如下update方法可能会把config覆盖掉,之后再考虑怎么做 + public void Update(long key, string value) + { + keyCache.TryRemove(key.ToString(), out _); + liteCollection.Update(new KV(key.ToString(), value)); + } + + public void UpdateByName(string userName, string newUserName, string value) + { + //存在修改索引的操作,所以删除后增加 + keyCache.TryRemove(userName, out _); + //liteCollection. + //liteCollection.Update(userName,new KV(newUserName, value)); + liteCollection.Delete(userName); + liteCollection.Insert(new KV(newUserName, value)); + } + + //public void UpdateByName(string userName, string value) + //{ + // keyCache.TryRemove(userName, out _); + // liteCollection.Update(new KV(userName, value)); + //} + public int GetCount() + { + return liteCollection.Count(); + } + + public List Select(int startIndex, int length) + { + return liteCollection.FindAll().Select(kv => kv.Value).ToList(); + } + + public string GetConfig(string userId) + { + var obj = liteCollection.FindById(userId); + if (obj != null) + { + return obj.Config; + } + + return null; + } + + public void SetConfig(string userId, string config) + { + var obj = liteCollection.FindById(userId); + obj.Config = config; + liteCollection.Update(obj); + } + + public string Get(long key) + { + return Get(key.ToString()); + } + + public string Get(string key) + { + if (keyCache.ContainsKey(key)) + { + return keyCache[key]; + } + else + { + var obj = liteCollection.FindById(key); + if (obj != null) + { + keyCache.TryAdd(key, obj.Value); + return obj.Value; + } + } + return null; + } + + public void Delete(int index) + { + //liteCollection.Delete() + //no implementation + } + + public void DeleteHash(string key) + { + liteCollection.Delete(new BsonValue(key)); + keyCache.TryRemove(key, out _); + } + + public long GetLength() + { + try + { + return liteCollection.Count(); + } + catch (NullReferenceException ex) + { + _ = ex; + return 0; + } + } + + + public void Close() + { + if (!isClosed) + { + this.Dispose(); + isClosed = true; + } + } + + public bool Exist(string key) + { + if (keyCache.ContainsKey(key)) + { + return true; + } + else + { + var obj = liteCollection.FindById(key); + if (obj != null) + { + keyCache.TryAdd(key, obj.Value); + return true; + } + } + return false; + } + + public bool Exist(long key) + { + return Exist(key.ToString()); + } + + private Random rand = new Random(); + + /// + /// 获取可用的随机索引 + /// + /// + private long GetAvailableKey(int maxValue) + { + int key = 0; + while (true) + { + key = rand.Next(65535); + if (!Exist(key)) + { + return key; + } + } + } + } + + public class KV + { + public KV() + { + } + + public KV(string k, string val) + { + Key = k; + Value = val; + } + + [BsonId] + public string Key { get; set; } + public string Value { get; set; } + public string Config { get; set; }//客户端配置后期指定 + } +} diff --git a/src/NSmartProxy/Database/NSmartDb.cs b/src/NSmartProxy/Database/NSmartDb.cs index 9bbf684..0922e07 100644 --- a/src/NSmartProxy/Database/NSmartDb.cs +++ b/src/NSmartProxy/Database/NSmartDb.cs @@ -11,8 +11,8 @@ namespace NSmartProxy.Database /// public class NSmartDbOperator : IDbOperator { - public const string SUPER_VARIABLE_INDEX_ID = "$index_id$"; + public const string SUPER_VARIABLE_INDEX_ID = "$index_id$"; private SequenceFile seqf; private HashFile hashf; private string hashfFile; @@ -99,6 +99,16 @@ public void Update(long key, string value) hashf.Put(keyBytesbytes, valBytes); } + public void UpdateByName(string userName, string newUserName, string value) + { + throw new NotImplementedException(); + } + + public void UpdateByName(string userName, string value) + { + throw new NotImplementedException(); + } + public List Select(int startIndex, int length) { List strs = new List(length); @@ -122,6 +132,16 @@ public List Select(int startIndex, int length) //输出 } + public string GetConfig(string userId) + { + throw new NotImplementedException(); + } + + public void SetConfig(string userId, string config) + { + throw new NotImplementedException(); + } + /// /// 通过序列删除数据,并且返回id /// @@ -150,6 +170,11 @@ public bool Exist(string key) return hashf.Exist(String2Bytes(key)); } + public int GetCount() + { + throw new NotImplementedException(); + } + public bool Exist(long key) { return hashf.Exist(BitConverter.GetBytes(key)); diff --git a/src/NSmartProxy/Database/SequenceFile.cs b/src/NSmartProxy/Database/SequenceFile.cs index 9b299d2..831606d 100644 --- a/src/NSmartProxy/Database/SequenceFile.cs +++ b/src/NSmartProxy/Database/SequenceFile.cs @@ -10,9 +10,9 @@ public class SequenceFile private FileStream rf; private BinaryWriter bw; private BinaryReader br; - private long tableSize = 1024L; //1k + private readonly long tableSize = 1024L; //1k private const int SEQUENCE_ITEM_LENGTH = 8; - private string filePath; + private readonly string filePath; public SequenceFile(String file) { filePath = file; @@ -33,7 +33,7 @@ private void Init(string file) bw.Write(0L); } } - catch (Exception ex) + catch //(Exception ex) { bw.Close(); br.Close(); rf.Close(); throw; @@ -100,6 +100,7 @@ public void Delete(int index) bw.Write(0L); rf.Position = (index + 1) * SEQUENCE_ITEM_LENGTH; bw.Write(lastval); + bw.Flush();//连续删除有时会出bug,尝试提前flush到文件中 TODO 待验证 } public void Clear() diff --git a/src/NSmartProxy/Extension/CAGen.cs b/src/NSmartProxy/Extension/CAGen.cs new file mode 100644 index 0000000..5254294 --- /dev/null +++ b/src/NSmartProxy/Extension/CAGen.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace NSmartProxy.Extension +{ + public class CAGen + { + /// + /// 生成CA,TODO 2 如何通过根证书生成接下来的证书 + /// + /// + /// + /// + public static X509Certificate2 GenerateCA(string CertificateName,string hosts = null) + { + SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddIpAddress(IPAddress.Loopback); + sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); + sanBuilder.AddDnsName("localhost"); + if (hosts != null) + { + string[] strings = hosts.Split(','); + foreach (var str in strings) + { + sanBuilder.AddDnsName(str); + } + } + + sanBuilder.AddDnsName(Environment.MachineName); + + X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={CertificateName}"); + + using (RSA rsa = RSA.Create(2048)) + { + var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + request.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); + + + request.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false)); + + request.CertificateExtensions.Add(sanBuilder.Build()); + + var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650))); + //certificate.FriendlyName = CertificateName; + //return certificate; + + return new X509Certificate2(certificate.Export(X509ContentType.Pfx, "WeNeedASaf3rPassword"), + "WeNeedASaf3rPassword", X509KeyStorageFlags.Exportable); + + } + } + } +} diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/config.html b/src/NSmartProxy/Extension/HttpServerStaticFiles/config.html deleted file mode 100644 index 5426829..0000000 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/config.html +++ /dev/null @@ -1,23 +0,0 @@ -
-

配置

-
-
- -
- -
-
- -
- - - -
\ No newline at end of file diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/config.js b/src/NSmartProxy/Extension/HttpServerStaticFiles/config.js deleted file mode 100644 index 276d6f2..0000000 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/config.js +++ /dev/null @@ -1,27 +0,0 @@ -//@ sourceURL= config.js -function getConfig(key, func) { - $.get(basepath + "GetConfig?key=" + key, function (res) { - - $("#config" + key).prop("checked", res.Data == "1"); - - }); -} - -$(document).ready(function () { - getConfig("AllowAnonymousUser"); -}); - -function toggleConfig(obj) { - var cbx = $(obj).find("input[type='checkbox']"); - var key = cbx.attr("id").replace("config", ""); - var value = cbx.prop("checked") == true ? "1" : "0"; - if (window.conDelayTask) window.clearTimeout(conDelayTask); - window.conDelayTask = window.setTimeout('setConfig("' + key + '","' + value + '")', 10);//hack -} - -function setConfig(key, value) { - $.get(basepath + "SetConfig?key=" + key + "&value=" + value, function (res) { - //$("#config" + key).prop("checked", res.Data == "1"); - console.log("设置成功"); - }); -} \ No newline at end of file diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/feather.min.js b/src/NSmartProxy/Extension/HttpServerStaticFiles/feather.min.js deleted file mode 100644 index 7f61d3e..0000000 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/feather.min.js +++ /dev/null @@ -1,13 +0,0 @@ -!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.feather=n():e.feather=n()}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function i(l){if(n[l])return n[l].exports;var t=n[l]={i:l,l:!1,exports:{}};return e[l].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=e,i.c=n,i.d=function(e,n,l){i.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:l})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,"a",n),n},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.p="",i(i.s=61)}([function(e,n,i){var l=i(20)("wks"),t=i(11),r=i(1).Symbol,o="function"==typeof r;(e.exports=function(e){return l[e]||(l[e]=o&&r[e]||(o?r:t)("Symbol."+e))}).store=l},function(e,n){var i=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=i)},function(e,n){var i=e.exports={version:"2.5.6"};"number"==typeof __e&&(__e=i)},function(e,n){var i={}.hasOwnProperty;e.exports=function(e,n){return i.call(e,n)}},function(e,n,i){e.exports=!i(27)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,n,i){var l=i(13);e.exports=function(e){if(!l(e))throw TypeError(e+" is not an object!");return e}},function(e,n,i){var l=i(5),t=i(56),r=i(55),o=Object.defineProperty;n.f=i(4)?Object.defineProperty:function(e,n,i){if(l(e),n=r(n,!0),l(i),t)try{return o(e,n,i)}catch(e){}if("get"in i||"set"in i)throw TypeError("Accessors not supported!");return"value"in i&&(e[n]=i.value),e}},function(e,n,i){var l=i(6),t=i(12);e.exports=i(4)?function(e,n,i){return l.f(e,n,t(1,i))}:function(e,n,i){return e[n]=i,e}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var l=o(i(35)),t=o(i(33)),r=o(i(32));function o(e){return e&&e.__esModule?e:{default:e}}n.default=Object.keys(t.default).map(function(e){return new l.default(e,t.default[e],r.default[e])}).reduce(function(e,n){return e[n.name]=n,e},{})},function(e,n,i){var l=i(20)("keys"),t=i(11);e.exports=function(e){return l[e]||(l[e]=t(e))}},function(e,n){e.exports={}},function(e,n){var i=0,l=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+l).toString(36))}},function(e,n){e.exports=function(e,n){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:n}}},function(e,n){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,n){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,n){var i=Math.ceil,l=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?l:i)(e)}},function(e,n,i){var l; -/*! - Copyright (c) 2016 Jed Watson. - Licensed under the MIT License (MIT), see - http://jedwatson.github.io/classnames -*/ -/*! - Copyright (c) 2016 Jed Watson. - Licensed under the MIT License (MIT), see - http://jedwatson.github.io/classnames -*/ -!function(){"use strict";var i=function(){function e(){}function n(e,n){for(var i=n.length,l=0;l0?t(l(e),9007199254740991):0}},function(e,n){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,n,i){var l=i(48),t=i(14);e.exports=function(e){return l(t(e))}},function(e,n,i){var l=i(54);e.exports=function(e,n,i){if(l(e),void 0===n)return e;switch(i){case 1:return function(i){return e.call(n,i)};case 2:return function(i,l){return e.call(n,i,l)};case 3:return function(i,l,t){return e.call(n,i,l,t)}}return function(){return e.apply(n,arguments)}}},function(e,n,i){var l=i(1),t=i(7),r=i(3),o=i(11)("src"),a=Function.toString,c=(""+a).split("toString");i(2).inspectSource=function(e){return a.call(e)},(e.exports=function(e,n,i,a){var y="function"==typeof i;y&&(r(i,"name")||t(i,"name",n)),e[n]!==i&&(y&&(r(i,o)||t(i,o,e[n]?""+e[n]:c.join(String(n)))),e===l?e[n]=i:a?e[n]?e[n]=i:t(e,n,i):(delete e[n],t(e,n,i)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[o]||a.call(this)})},function(e,n,i){var l=i(13),t=i(1).document,r=l(t)&&l(t.createElement);e.exports=function(e){return r?t.createElement(e):{}}},function(e,n){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,n,i){var l=i(1),t=i(2),r=i(7),o=i(25),a=i(24),c=function(e,n,i){var y,p,h,x,s=e&c.F,u=e&c.G,d=e&c.S,f=e&c.P,v=e&c.B,g=u?l:d?l[n]||(l[n]={}):(l[n]||{}).prototype,m=u?t:t[n]||(t[n]={}),M=m.prototype||(m.prototype={});for(y in u&&(i=n),i)h=((p=!s&&g&&void 0!==g[y])?g:i)[y],x=v&&p?a(h,l):f&&"function"==typeof h?a(Function.call,h):h,g&&o(g,y,h,e&c.U),m[y]!=h&&r(m,y,x),f&&M[y]!=h&&(M[y]=h)};l.core=t,c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,e.exports=c},function(e,n){e.exports=!1},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var l=Object.assign||function(e){for(var n=1;n0&&void 0!==arguments[0]?arguments[0]:{};if("undefined"==typeof document)throw new Error("`feather.replace()` only works in a browser environment.");var n=document.querySelectorAll("[data-feather]");Array.from(n).forEach(function(n){return function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=function(e){return Array.from(e.attributes).reduce(function(e,n){return e[n.name]=n.value,e},{})}(e),o=i["data-feather"];delete i["data-feather"];var a=r.default[o].toSvg(l({},n,i,{class:(0,t.default)(n.class,i.class)})),c=(new DOMParser).parseFromString(a,"image/svg+xml").querySelector("svg");e.parentNode.replaceChild(c,e)}(n,e)})}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var l,t=i(8),r=(l=t)&&l.__esModule?l:{default:l};n.default=function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."),!e)throw new Error("The required `key` (icon name) parameter is missing.");if(!r.default[e])throw new Error("No icon matching '"+e+"'. See the complete list of icons at https://feathericons.com");return r.default[e].toSvg(n)}},function(e){e.exports={activity:["pulse","health","action","motion"],airplay:["stream","cast","mirroring"],"alert-circle":["warning"],"alert-octagon":["warning"],"alert-triangle":["warning"],"at-sign":["mention"],award:["achievement","badge"],aperture:["camera","photo"],bell:["alarm","notification"],"bell-off":["alarm","notification","silent"],bluetooth:["wireless"],"book-open":["read"],book:["read","dictionary","booklet","magazine"],bookmark:["read","clip","marker","tag"],briefcase:["work","bag","baggage","folder"],clipboard:["copy"],clock:["time","watch","alarm"],"cloud-drizzle":["weather","shower"],"cloud-lightning":["weather","bolt"],"cloud-rain":["weather"],"cloud-snow":["weather","blizzard"],cloud:["weather"],codepen:["logo"],coffee:["drink","cup","mug","tea","cafe","hot","beverage"],command:["keyboard","cmd"],compass:["navigation","safari","travel"],copy:["clone","duplicate"],"corner-down-left":["arrow"],"corner-down-right":["arrow"],"corner-left-down":["arrow"],"corner-left-up":["arrow"],"corner-right-down":["arrow"],"corner-right-up":["arrow"],"corner-up-left":["arrow"],"corner-up-right":["arrow"],"credit-card":["purchase","payment","cc"],crop:["photo","image"],crosshair:["aim","target"],database:["storage"],delete:["remove"],disc:["album","cd","dvd","music"],"dollar-sign":["currency","money","payment"],droplet:["water"],edit:["pencil","change"],"edit-2":["pencil","change"],"edit-3":["pencil","change"],eye:["view","watch"],"eye-off":["view","watch"],"external-link":["outbound"],facebook:["logo"],"fast-forward":["music"],film:["movie","video"],"folder-minus":["directory"],"folder-plus":["directory"],folder:["directory"],gift:["present","box","birthday","party"],"git-branch":["code","version control"],"git-commit":["code","version control"],"git-merge":["code","version control"],"git-pull-request":["code","version control"],github:["logo","version control"],gitlab:["logo","version control"],global:["world","browser","language","translate"],"hard-drive":["computer","server"],hash:["hashtag","number","pound"],headphones:["music","audio"],heart:["like","love"],"help-circle":["question mark"],home:["house"],image:["picture"],inbox:["email"],instagram:["logo","camera"],"life-bouy":["help","life ring","support"],linkedin:["logo"],lock:["security","password"],"log-in":["sign in","arrow"],"log-out":["sign out","arrow"],mail:["email"],"map-pin":["location","navigation","travel","marker"],map:["location","navigation","travel"],maximize:["fullscreen"],"maximize-2":["fullscreen","arrows"],menu:["bars","navigation","hamburger"],"message-circle":["comment","chat"],"message-square":["comment","chat"],"mic-off":["record"],mic:["record"],minimize:["exit fullscreen"],"minimize-2":["exit fullscreen","arrows"],monitor:["tv"],moon:["dark","night"],"more-horizontal":["ellipsis"],"more-vertical":["ellipsis"],move:["arrows"],navigation:["location","travel"],"navigation-2":["location","travel"],octagon:["stop"],package:["box"],paperclip:["attachment"],pause:["music","stop"],"pause-circle":["music","stop"],play:["music","start"],"play-circle":["music","start"],plus:["add","new"],"plus-circle":["add","new"],"plus-square":["add","new"],pocket:["logo","save"],power:["on","off"],radio:["signal"],rewind:["music"],rss:["feed","subscribe"],save:["floppy disk"],send:["message","mail","paper airplane"],settings:["cog","edit","gear","preferences"],shield:["security"],"shield-off":["security"],"shopping-bag":["ecommerce","cart","purchase","store"],"shopping-cart":["ecommerce","cart","purchase","store"],shuffle:["music"],"skip-back":["music"],"skip-forward":["music"],slash:["ban","no"],sliders:["settings","controls"],speaker:["music"],star:["bookmark","favorite","like"],sun:["brightness","weather","light"],sunrise:["weather"],sunset:["weather"],tag:["label"],target:["bullseye"],terminal:["code","command line"],"thumbs-down":["dislike","bad"],"thumbs-up":["like","good"],"toggle-left":["on","off","switch"],"toggle-right":["on","off","switch"],trash:["garbage","delete","remove"],"trash-2":["garbage","delete","remove"],triangle:["delta"],truck:["delivery","van","shipping"],twitter:["logo"],umbrella:["rain","weather"],"video-off":["camera","movie","film"],video:["camera","movie","film"],voicemail:["phone"],volume:["music","sound","mute"],"volume-1":["music","sound"],"volume-2":["music","sound"],"volume-x":["music","sound","mute"],watch:["clock","time"],wind:["weather","air"],"x-circle":["cancel","close","delete","remove","times"],"x-square":["cancel","close","delete","remove","times"],x:["cancel","close","delete","remove","times"],youtube:["logo","video","play"],"zap-off":["flash","camera","lightning"],zap:["flash","camera","lightning"]}},function(e){e.exports={activity:'',airplay:'',"alert-circle":'',"alert-octagon":'',"alert-triangle":'',"align-center":'',"align-justify":'',"align-left":'',"align-right":'',anchor:'',aperture:'',archive:'',"arrow-down-circle":'',"arrow-down-left":'',"arrow-down-right":'',"arrow-down":'',"arrow-left-circle":'',"arrow-left":'',"arrow-right-circle":'',"arrow-right":'',"arrow-up-circle":'',"arrow-up-left":'',"arrow-up-right":'',"arrow-up":'',"at-sign":'',award:'',"bar-chart-2":'',"bar-chart":'',"battery-charging":'',battery:'',"bell-off":'',bell:'',bluetooth:'',bold:'',"book-open":'',book:'',bookmark:'',box:'',briefcase:'',calendar:'',"camera-off":'',camera:'',cast:'',"check-circle":'',"check-square":'',check:'',"chevron-down":'',"chevron-left":'',"chevron-right":'',"chevron-up":'',"chevrons-down":'',"chevrons-left":'',"chevrons-right":'',"chevrons-up":'',chrome:'',circle:'',clipboard:'',clock:'',"cloud-drizzle":'',"cloud-lightning":'',"cloud-off":'',"cloud-rain":'',"cloud-snow":'',cloud:'',code:'',codepen:'',coffee:'',command:'',compass:'',copy:'',"corner-down-left":'',"corner-down-right":'',"corner-left-down":'',"corner-left-up":'',"corner-right-down":'',"corner-right-up":'',"corner-up-left":'',"corner-up-right":'',cpu:'',"credit-card":'',crop:'',crosshair:'',database:'',delete:'',disc:'',"dollar-sign":'',"download-cloud":'',download:'',droplet:'',"edit-2":'',"edit-3":'',edit:'',"external-link":'',"eye-off":'',eye:'',facebook:'',"fast-forward":'',feather:'',"file-minus":'',"file-plus":'',"file-text":'',file:'',film:'',filter:'',flag:'',"folder-minus":'',"folder-plus":'',folder:'',gift:'',"git-branch":'',"git-commit":'',"git-merge":'',"git-pull-request":'',github:'',gitlab:'',globe:'',grid:'',"hard-drive":'',hash:'',headphones:'',heart:'',"help-circle":'',home:'',image:'',inbox:'',info:'',instagram:'',italic:'',layers:'',layout:'',"life-buoy":'',"link-2":'',link:'',linkedin:'',list:'',loader:'',lock:'',"log-in":'',"log-out":'',mail:'',"map-pin":'',map:'',"maximize-2":'',maximize:'',menu:'',"message-circle":'',"message-square":'',"mic-off":'',mic:'',"minimize-2":'',minimize:'',"minus-circle":'',"minus-square":'',minus:'',monitor:'',moon:'',"more-horizontal":'',"more-vertical":'',move:'',music:'',"navigation-2":'',navigation:'',octagon:'',package:'',paperclip:'',"pause-circle":'',pause:'',percent:'',"phone-call":'',"phone-forwarded":'',"phone-incoming":'',"phone-missed":'',"phone-off":'',"phone-outgoing":'',phone:'',"pie-chart":'',"play-circle":'',play:'',"plus-circle":'',"plus-square":'',plus:'',pocket:'',power:'',printer:'',radio:'',"refresh-ccw":'',"refresh-cw":'',repeat:'',rewind:'',"rotate-ccw":'',"rotate-cw":'',rss:'',save:'',scissors:'',search:'',send:'',server:'',settings:'',"share-2":'',share:'',"shield-off":'',shield:'',"shopping-bag":'',"shopping-cart":'',shuffle:'',sidebar:'',"skip-back":'',"skip-forward":'',slack:'',slash:'',sliders:'',smartphone:'',speaker:'',square:'',star:'',"stop-circle":'',sun:'',sunrise:'',sunset:'',tablet:'',tag:'',target:'',terminal:'',thermometer:'',"thumbs-down":'',"thumbs-up":'',"toggle-left":'',"toggle-right":'',"trash-2":'',trash:'',trello:'',"trending-down":'',"trending-up":'',triangle:'',truck:'',tv:'',twitter:'',type:'',umbrella:'',underline:'',unlock:'',"upload-cloud":'',upload:'',"user-check":'',"user-minus":'',"user-plus":'',"user-x":'',user:'',users:'',"video-off":'',video:'',voicemail:'',"volume-1":'',"volume-2":'',"volume-x":'',volume:'',watch:'',"wifi-off":'',wifi:'',wind:'',"x-circle":'',"x-square":'',x:'',youtube:'',"zap-off":'',zap:'',"zoom-in":'',"zoom-out":''}},function(e){e.exports={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var l=Object.assign||function(e){for(var n=1;n2&&void 0!==arguments[2]?arguments[2]:[];!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.name=n,this.contents=i,this.tags=t,this.attrs=l({},o.default,{class:"feather feather-"+n})}return t(e,[{key:"toSvg",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return""+this.contents+""}},{key:"toString",value:function(){return this.contents}}]),e}();n.default=c},function(e,n,i){"use strict";var l=o(i(8)),t=o(i(31)),r=o(i(30));function o(e){return e&&e.__esModule?e:{default:e}}e.exports={icons:l.default,toSvg:t.default,replace:r.default}},function(e,n,i){var l=i(0)("iterator"),t=!1;try{var r=[7][l]();r.return=function(){t=!0},Array.from(r,function(){throw 2})}catch(e){}e.exports=function(e,n){if(!n&&!t)return!1;var i=!1;try{var r=[7],o=r[l]();o.next=function(){return{done:i=!0}},r[l]=function(){return o},e(r)}catch(e){}return i}},function(e,n,i){var l=i(22),t=i(0)("toStringTag"),r="Arguments"==l(function(){return arguments}());e.exports=function(e){var n,i,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,n){try{return e[n]}catch(e){}}(n=Object(e),t))?i:r?l(n):"Object"==(o=l(n))&&"function"==typeof n.callee?"Arguments":o}},function(e,n,i){var l=i(38),t=i(0)("iterator"),r=i(10);e.exports=i(2).getIteratorMethod=function(e){if(void 0!=e)return e[t]||e["@@iterator"]||r[l(e)]}},function(e,n,i){"use strict";var l=i(6),t=i(12);e.exports=function(e,n,i){n in e?l.f(e,n,t(0,i)):e[n]=i}},function(e,n,i){var l=i(10),t=i(0)("iterator"),r=Array.prototype;e.exports=function(e){return void 0!==e&&(l.Array===e||r[t]===e)}},function(e,n,i){var l=i(5);e.exports=function(e,n,i,t){try{return t?n(l(i)[0],i[1]):n(i)}catch(n){var r=e.return;throw void 0!==r&&l(r.call(e)),n}}},function(e,n,i){"use strict";var l=i(24),t=i(28),r=i(17),o=i(42),a=i(41),c=i(21),y=i(40),p=i(39);t(t.S+t.F*!i(37)(function(e){Array.from(e)}),"Array",{from:function(e){var n,i,t,h,x=r(e),s="function"==typeof this?this:Array,u=arguments.length,d=u>1?arguments[1]:void 0,f=void 0!==d,v=0,g=p(x);if(f&&(d=l(d,u>2?arguments[2]:void 0,2)),void 0==g||s==Array&&a(g))for(i=new s(n=c(x.length));n>v;v++)y(i,v,f?d(x[v],v):x[v]);else for(h=g.call(x),i=new s;!(t=h.next()).done;v++)y(i,v,f?o(h,d,[t.value,v],!0):t.value);return i.length=v,i}})},function(e,n,i){var l=i(3),t=i(17),r=i(9)("IE_PROTO"),o=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=t(e),l(e,r)?e[r]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?o:null}},function(e,n,i){var l=i(1).document;e.exports=l&&l.documentElement},function(e,n,i){var l=i(15),t=Math.max,r=Math.min;e.exports=function(e,n){return(e=l(e))<0?t(e+n,0):r(e,n)}},function(e,n,i){var l=i(23),t=i(21),r=i(46);e.exports=function(e){return function(n,i,o){var a,c=l(n),y=t(c.length),p=r(o,y);if(e&&i!=i){for(;y>p;)if((a=c[p++])!=a)return!0}else for(;y>p;p++)if((e||p in c)&&c[p]===i)return e||p||0;return!e&&-1}}},function(e,n,i){var l=i(22);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==l(e)?e.split(""):Object(e)}},function(e,n,i){var l=i(3),t=i(23),r=i(47)(!1),o=i(9)("IE_PROTO");e.exports=function(e,n){var i,a=t(e),c=0,y=[];for(i in a)i!=o&&l(a,i)&&y.push(i);for(;n.length>c;)l(a,i=n[c++])&&(~r(y,i)||y.push(i));return y}},function(e,n,i){var l=i(49),t=i(19);e.exports=Object.keys||function(e){return l(e,t)}},function(e,n,i){var l=i(6),t=i(5),r=i(50);e.exports=i(4)?Object.defineProperties:function(e,n){t(e);for(var i,o=r(n),a=o.length,c=0;a>c;)l.f(e,i=o[c++],n[i]);return e}},function(e,n,i){var l=i(5),t=i(51),r=i(19),o=i(9)("IE_PROTO"),a=function(){},c=function(){var e,n=i(26)("iframe"),l=r.length;for(n.style.display="none",i(45).appendChild(n),n.src="javascript:",(e=n.contentWindow.document).open(),e.write(" - ", token); + ", Global.TOKEN_COOKIE_NAME, token); } /// @@ -186,7 +244,6 @@ public string Login(string username, string userpwd) public LoginFormClientResult LoginFromClient(string username, string userpwd) { User user = null; - string isAnonymous = "0"; //匿名登录时創建一個用戶 if (ServerContext.SupportAnonymousLogin && string.IsNullOrEmpty(username)) { @@ -195,7 +252,7 @@ public LoginFormClientResult LoginFromClient(string username, string userpwd) userpwd = RandomHelper.NextString(20); user = new User { - userId = NSmartDbOperator.SUPER_VARIABLE_INDEX_ID, //索引id + userId = SUPER_VARIABLE_INDEX_ID, //索引id userName = username, userPwd = EncryptHelper.SHA256(userpwd), regTime = DateTime.Now.ToString(), @@ -227,7 +284,7 @@ public LoginFormClientResult LoginFromClient(string username, string userpwd) //2.给token string output = $"{username}|{DateTime.Now.ToString("yyyy-MM-dd")}"; string token = EncryptHelper.AES_Encrypt(output); - return new LoginFormClientResult { Token = token, Version = Global.NSmartProxyServerName, Userid = user.userId }; + return new LoginFormClientResult { Token = token, Version = NSPVersion.NSmartProxyServerName, Userid = user.userId }; } #endregion @@ -250,7 +307,7 @@ public void AddUserV2(string userName, string userpwd, string isAdmin) } var user = new User { - userId = NSmartDbOperator.SUPER_VARIABLE_INDEX_ID, //索引id + userId = SUPER_VARIABLE_INDEX_ID, //索引id userName = userName, userPwd = EncryptHelper.SHA256(userpwd), regTime = DateTime.Now.ToString(), @@ -261,6 +318,46 @@ public void AddUserV2(string userName, string userpwd, string isAdmin) Dbop.Insert(userName, user.ToJsonString()); } + /// + /// + /// + /// + /// + /// 1代表是 0代表否 + [API] + [Secure] + public void UpdateUser(string oldUserName, string newUserName, string userPwd, string isAdmin) + { + + if (!Dbop.Exist(oldUserName)) + { + throw new Exception($"error: user {oldUserName} not exist."); + } + if (newUserName != oldUserName && Dbop.Exist(newUserName)) + { + throw new Exception($"error: user {newUserName} exist."); + } + //var user = new User + //{ + // userId = SUPER_VARIABLE_INDEX_ID, //索引id + // userName = userName, + // userPwd = EncryptHelper.SHA256(userpwd), + // regTime = DateTime.Now.ToString(), + // isAdmin = isAdmin + //}; + User user = Dbop.Get(oldUserName)?.ToObject(); + user.isAdmin = isAdmin; + user.userName = newUserName; + if (userPwd != "XXXXXXXX") + { + user.userPwd = EncryptHelper.SHA256(userPwd); + } + + //if (isAdmin == true) user. + //1.增加用户 + Dbop.UpdateByName(oldUserName, newUserName, user.ToJsonString()); + } + [API] @@ -271,11 +368,11 @@ public void RemoveUser(string userIndex, string userNames) { var arr = userIndex.Split(','); var userNameArr = userNames.Split(','); - for (var i = arr.Length - 1; i > -1; i--) - { - Dbop.Delete(int.Parse(arr[i])); - Dbop.DeleteHash(userNameArr[i]); - } + //for (var i = arr.Length - 1; i > -1; i--) + //{ + // Dbop.Delete(int.Parse(arr[i])); + // Dbop.DeleteHash(userNameArr[i]); + //} //删除用户绑定 lock (userLocker) @@ -286,6 +383,14 @@ public void RemoveUser(string userIndex, string userNames) //刷新绑定列表 ServerContext.UpdatePortMap(); ServerContext.ServerConfig.SaveChanges(ServerContext.ServerConfigPath); + for (var i = arr.Length - 1; i > -1; i--) + { + var userId = int.Parse(arr[i]); + var userDto = Dbop.Get(userNameArr[i]).ToObject(); + Dbop.Delete(userId);//litedb不起作用 + Dbop.DeleteHash(userNameArr[i]); + ServerContext.CloseAllSourceByClient(int.Parse(userDto.userId)); + } } catch (Exception ex) { @@ -322,9 +427,68 @@ public List GetUsers() [ValidateAPI] [Secure] - public bool ValidateUserName(string username) + public bool ValidateUserName(string isEdit, string oldUsername, string newUserName) { - return !Dbop.Exist(username); + if (isEdit == "1" && oldUsername == newUserName) + { + return true; + } + + return !Dbop.Exist(newUserName); + + } + + [API] + [Secure] + public NSPClientConfig GetServerClientConfig(string userId = null) + { + if (String.IsNullOrWhiteSpace(userId)) + { + var claims = + StringUtil.ConvertStringToTokenClaims(HttpContext.Request.Cookies[Global.TOKEN_COOKIE_NAME].Value); + userId = claims.UserKey; + } + + var config = Dbop.GetConfig(userId)?.ToObject(); + return config; + } + + [API] + [Secure] + public void SetServerClientConfig(string userName, string config) + { + NSPClientConfig nspClientConfig = null; + if (String.IsNullOrWhiteSpace(config))//用户如果清空了配置则客户端会自行使用自己的配置文件 + { + Dbop.SetConfig(userName, ""); + } + else + { + try + { + nspClientConfig = config.ToObject(); + nspClientConfig.UseServerControl = true; + //nspClientConfig.ProviderAddress = HttpContext.Request.Url.Host; + // nspClientConfig.ProviderWebPort = ServerContext.ServerConfig.WebAPIPort; + // nspClientConfig.ConfigPort = ServerContext.ServerConfig.ConfigPort; + // nspClientConfig.ReversePort = ServerContext.ServerConfig.ReversePort; + } + catch (Exception e) + { + throw new Exception("配置格式不正确。"); + } + + Dbop.SetConfig(userName, nspClientConfig.ToJsonString()); + } + + //重置客户端(给客户端发送重定向请求让客户端主动重启) + var userid = Dbop.Get(userName)?.ToObject().userId; + //var popClientAsync = await ServerContext.Clients[userid].AppMap.First().Value.PopClientAsync(); + + ServerContext.CloseAllSourceByClient(int.Parse(userid)); + + //ServerContext.CloseAllSourceByClient(); + //return new NSPClientConfig(); } #endregion @@ -339,69 +503,100 @@ public string GetClientsInfoJson() StringBuilder json = new StringBuilder("[ "); foreach (var (key, value) in ServerContext.PortAppMap) { - json.Append("{ "); - json.Append(KV2Json("port", key)).C(); - json.Append(KV2Json("clientId", value.ClientId)).C(); - json.Append(KV2Json("appId", value.AppId)).C(); - json.Append(KV2Json("blocksCount", value.TcpClientBlocks.Count)).C(); - //反向连接 - json.Append(KV2Json("revconns")); - json.Append("[ "); - foreach (var reverseClient in value.ReverseClients) + + if (value.Count > 0) { - json.Append("{ "); - if (reverseClient.Connected) + foreach (var (key2, value2) in value) { - json.Append(KV2Json("lEndPoint", reverseClient.Client.LocalEndPoint.ToString())).C(); - json.Append(KV2Json("rEndPoint", reverseClient.Client.RemoteEndPoint.ToString())); + AddAppJsonItem(json, key, value2); } - - //json.Append(KV2Json("p", c)).C(); - //json.Append(KV2Json("port", ca.Key)); - json.Append("}"); - json.C(); } + else + { + AddAppJsonItem(json, key, value.ActivateApp); + } + } - json.D(); - json.Append("]").C(); + foreach (var (key, value) in ServerContext.UDPPortAppMap) + { - //隧道状态 - json.Append(KV2Json("tunnels")); - json.Append("[ "); - foreach (var tunnel in value.Tunnels) + if (value.Count > 0) { - json.Append("{ "); - if (tunnel.ClientServerClient != null) - { - Socket sktClient = tunnel.ClientServerClient.Client; - if (tunnel.ClientServerClient.Connected) - - json.Append(KV2Json("clientServerClient", $"{sktClient.LocalEndPoint}-{sktClient.RemoteEndPoint}")) - .C(); - } - if (tunnel.ConsumerClient != null) + foreach (var (key2, value2) in value) { - Socket sktConsumer = tunnel.ConsumerClient.Client; - if (tunnel.ConsumerClient.Connected) - json.Append(KV2Json("consumerClient", $"{sktConsumer.LocalEndPoint}-{sktConsumer.RemoteEndPoint}")) - .C(); + AddAppJsonItem(json, key, value2); } + } + else + { + AddAppJsonItem(json, key, value.ActivateApp); + } + } + + json.D(); + json.Append("]"); + return json.ToString(); + } + + private void AddAppJsonItem(StringBuilder json, int key, NSPApp value) + { + json.Append("{ "); + json.Append(KV2Json("port", key)).C(); + json.Append(KV2Json("host", value.Host)).C(); + json.Append(KV2Json("clientId", value.ClientId)).C(); + json.Append(KV2Json("appId", value.AppId)).C(); + json.Append(KV2Json("blocksCount", value.TcpClientBlocks.Count)).C(); + json.Append(KV2Json("description", value.Description)).C(); + json.Append(KV2Json("protocol", Enum.GetName(typeof(Protocol), value.AppProtocol))).C(); + //反向连接 + json.Append(KV2Json("revconns")); + json.Append("[ "); + foreach (var reverseClient in value.ReverseClients) + { + json.Append("{ "); + if (reverseClient.Connected) + { + json.Append(KV2Json("lEndPoint", reverseClient.Client.LocalEndPoint.ToString())).C(); + json.Append(KV2Json("rEndPoint", reverseClient.Client.RemoteEndPoint.ToString())); + } + + json.Append("}"); + json.C(); + } - json.D(); - //json.Append(KV2Json("p", c)).C(); - //json.Append(KV2Json("port", ca.Key)); - json.Append("}"); - json.C(); + json.D(); + json.Append("]").C(); + + //隧道状态 + json.Append(KV2Json("tunnels")); + json.Append("[ "); + foreach (var tunnel in value.Tunnels) + { + json.Append("{ "); + if (tunnel.ClientServerClient != null) + { + Socket sktClient = tunnel.ClientServerClient.Client; + if (tunnel.ClientServerClient.Connected) + + json.Append(KV2Json("clientServerClient", $"{sktClient.LocalEndPoint}-{sktClient.RemoteEndPoint}")) + .C(); + } + if (tunnel.ConsumerClient != null) + { + Socket sktConsumer = tunnel.ConsumerClient.Client; + if (tunnel.ConsumerClient.Connected) + json.Append(KV2Json("consumerClient", $"{sktConsumer.LocalEndPoint}-{sktConsumer.RemoteEndPoint}")) + .C(); } json.D(); - json.Append("]"); - json.Append("}").C(); + json.Append("}"); + json.C(); } json.D(); json.Append("]"); - return json.ToString(); + json.Append("}").C(); } /// @@ -519,7 +714,6 @@ public string BindUserToPort(string userId, string ports) { string msg = $"端口{string.Join(',', unAvailabelPorts)}无法使用"; Server.Logger.Debug(msg); - //throw new Exception(msg); return msg; } //TODO 绑定端口到用户 @@ -553,17 +747,160 @@ private string KV2Json(string key) } private string KV2Json(string key, object value) { - return "\"" + key + "\":\"" + value.ToString() + "\""; + return "\"" + key + "\":\"" + value + "\""; } #endregion #endregion + #region ca + [API] + [Secure] + public List GetAllCA() + { + List caList = new List(); + + foreach (var (port, cert) in ServerContext.PortCertMap) + { + var cert2 = new X509Certificate2(cert); + caList.Add(new CertDTO() + { + CreateTime = cert2.GetEffectiveDateString(), + Port = int.Parse(port), + ToTime = cert2.GetExpirationDateString(), + Extensions = "证书名称:" + cert2.FriendlyName//FormatExtension(cert2.Extensions)性能太慢了 + }); + } + return caList; + } + private string FormatExtension(X509ExtensionCollection cert2Extensions) + { + StringBuilder extStr = new StringBuilder(); + foreach (var ext in cert2Extensions) + { + extStr.Append(ext.Oid.FriendlyName + ":"); + extStr.Append(ext.Format(true).Replace("\n\n", "
") + .Replace("\n", "
") + "
"); + } + return extStr.ToString(); + } + [API] + [Secure] + public string GenerateCA(string hosts) + { + var caName = RandomHelper.NextString(10, false); + X509Certificate2 ca = CAGen.GenerateCA(caName, hosts); + var export = ca.Export(X509ContentType.Pfx); + string baseLogPath = "./temp"; + string fileName = "_" + caName + ".pfx"; + string targetPath = baseLogPath + "/" + fileName; + DirectoryInfo dir = new DirectoryInfo(baseLogPath); + if (!dir.Exists) + { + dir.Create(); + } + + // File.Move(fileInfo.FullName, baseLogPath + "/" + port + ".pfx"); + File.WriteAllBytes(targetPath, export); + return fileName; + } + + [FileUpload] + [Secure] + public string UploadTempFile(FileInfo fileInfo) + { + string baseLogPath = "./temp"; + string targetPath = baseLogPath + "/" + fileInfo.Name; + DirectoryInfo dir = new DirectoryInfo(baseLogPath); + if (!dir.Exists) + { + dir.Create(); + } + + File.Move(fileInfo.FullName, targetPath); + + return Path.GetFileName(targetPath); + } + + [API] + [Secure] + public string AddCABound(string port, string filename) + { + if (!port.IsNum()) throw new Exception("port不是数字"); + int portInt = int.Parse(port); + string baseCAPath = "./ca"; + DirectoryInfo dir = new DirectoryInfo(baseCAPath); + if (!dir.Exists) + { + dir.Create(); + } + filename = Path.GetFileName(filename);//安全起见取一下文件名 + string destPath = baseCAPath + "/" + filename; + File.Move("./temp/" + filename, destPath); + ServerContext.PortCertMap[portInt.ToString()] = X509Certificate2.CreateFromCertFile(destPath); + // ServerContext.PortCertMap[port] = new X509Certificate2, + // "WeNeedASaf3rPassword", X509KeyStorageFlags.MachineKeySet); + ServerContext.ServerConfig.CABoundConfig[portInt.ToString()] = destPath; + ServerContext.SaveConfigChanges(); + return ""; + } + + [API] + [Secure] + public string DelCAFile(string filename) + { + //if (!port.IsNum()) throw new Exception("port不是数字"); + filename = Path.GetFileName(filename); + var filePath = "./ca/" + filename;//安全起见取一下文件名 + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + //string destPath = "./ca/" + filename; + //File.Move("./temp/" + filename, destPath); + //ServerContext.PortCertMap[port] = X509Certificate.CreateFromCertFile(destPath); + //ServerContext.ServerConfig.CABoundConfig[port] = destPath; + //删除文件,配置,以及内存中的绑定 + + ServerContext.SaveConfigChanges(); + return ""; + } + + [API] + [Secure] + public string DelCABound(string port) + { + if (!port.IsNum()) throw new Exception("port不是数字"); + + //删除文件,配置,以及内存中的绑定 + var filename = Path.GetFileName(ServerContext.ServerConfig.CABoundConfig[port]); + var filePath = "./ca/" + filename;//安全起见取一下文件名 + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + ServerContext.PortCertMap.Remove(port); + ServerContext.ServerConfig.CABoundConfig.Remove(port); + ServerContext.SaveConfigChanges(); + return "success"; + } + + #endregion + + + /// + /// 设置上下文 + /// + /// + public void SetContext(HttpListenerContext context) + { + HttpContext = context; + } } } diff --git a/src/NSmartProxy/Extension/PeekableBufferBlock.cs b/src/NSmartProxy/Extension/PeekableBufferBlock.cs new file mode 100644 index 0000000..ca84d18 --- /dev/null +++ b/src/NSmartProxy/Extension/PeekableBufferBlock.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; + +namespace NSmartProxy.Infrastructure.Extensions +{ + public class PeekableBufferBlock + { + private BufferBlock innerBufferBlock; + private Queue innerQueue; + + public void Post(T item) + { + innerQueue.Enqueue(item); + innerBufferBlock.Post(item); + } + + public async Task ReceiveAsync() + { + + await innerBufferBlock.ReceiveAsync(); + return innerQueue.Dequeue(); + } + + public T Receive() + { + + innerBufferBlock.Receive(); + return innerQueue.Dequeue(); + } + + public PeekableBufferBlock() + { + innerBufferBlock = new BufferBlock(); + innerQueue = new Queue(); + } + + public int Count => innerBufferBlock.Count; + + public T Peek() + { + if (innerQueue.Count == 0) return default(T); + return innerQueue.Peek(); + } + } +} diff --git a/src/NSmartProxy/NSPApp.cs b/src/NSmartProxy/NSPApp.cs index 5ebae5d..f981819 100644 --- a/src/NSmartProxy/NSPApp.cs +++ b/src/NSmartProxy/NSPApp.cs @@ -2,32 +2,44 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; +using NSmartProxy.Data; +using NSmartProxy.Infrastructure.Extensions; using NSmartProxy.Shared; namespace NSmartProxy { + + public class NSPApp { public int AppId; + public string Description;//app名字,用来标识app public int ClientId; public int ConsumePort; - public TcpListener Listener; + //public TcpListener Listener; public CancellationTokenSource CancelListenSource; - public BufferBlock TcpClientBlocks; //反向连接的阻塞队列,一般只有一个元素 + public PeekableBufferBlock TcpClientBlocks; //反向连接的阻塞队列,一般只有一个元素 public List Tunnels; //正在使用的隧道 public List ReverseClients; //反向连接的socket + public Protocol AppProtocol; //协议0 tcp 1 http + public string Host;//主机头 + //public X509Certificate2 Certificate;//证书 private bool _closed = false; + public bool IsClosed => _closed; + public bool IsCompress = false;//代表是否使用snappy压缩 public NSPApp() { CancelListenSource = new CancellationTokenSource(); - TcpClientBlocks = new BufferBlock(); + TcpClientBlocks = new PeekableBufferBlock(); Tunnels = new List(); ReverseClients = new List(); + //HttpApps = new Dictionary(); } /// @@ -35,9 +47,9 @@ public NSPApp() /// /// /// - public bool PushInComeClient(TcpClient incomeClient) + public void PushInComeClient(TcpClient incomeClient) { - return TcpClientBlocks.Post(incomeClient); + TcpClientBlocks.Post(incomeClient); } /// @@ -57,29 +69,60 @@ public async Task PopClientAsync() /// /// 关闭整个App /// - public int Close() + public int Close(bool isForceClose = false) { if (!_closed) { - int ClosedCount = 0; + int closedCount = 0; try { - Tunnels.ForEach((t) => + foreach (var t in Tunnels) { - t.ClientServerClient?.Close(); - t.ConsumerClient?.Close(); - ClosedCount++; - }); + //TODO 3调试用 + //Console.WriteLine("XXX"); + ////Console.WriteLine(t.ConsumerClient?.Client.LocalEndPoint); + //Console.WriteLine("XXX"); + if (t.ClientServerClient != null && t.ClientServerClient.Connected) + { + t.ClientServerClient.Close(); + } + + + if (t.ConsumerClient != null && t.ConsumerClient.Connected) + { + //关闭会直接出timewat + t.ConsumerClient.LingerState.Enabled = true; + t.ConsumerClient.LingerState.LingerTime = 0; + t.ConsumerClient.NoDelay = true; + //t.ConsumerClient.Client.Shutdown(SocketShutdown.Both); + t.ConsumerClient.Close(); + } + + closedCount++; + } + //关闭循环和当前的侦听 CancelListenSource?.Cancel(); - Listener?.Stop(); + //Listener?.Stop();//TODO 3 逻辑错误!这个侦听可能还共享给了其他的app //弹出TcpClientBlocks while (TcpClientBlocks.Count > 0) { - TcpClientBlocks.Receive().Close(); + TcpClient tcpClient = TcpClientBlocks.Receive(); + if (isForceClose) + { + try + { + tcpClient.GetStream().Write(new byte[] { (byte)ControlMethod.ForceClose }, 0, 1); + } + catch (Exception ex) + { + Server.Logger.Debug("尝试抢登强制关闭失败:" + ex.ToString()); + } + } + tcpClient.Close(); } _closed = true; - return ClosedCount; + return closedCount; } catch (Exception ex) { diff --git a/src/NSmartProxy/NSPAppGroup.cs b/src/NSmartProxy/NSPAppGroup.cs new file mode 100644 index 0000000..66f2017 --- /dev/null +++ b/src/NSmartProxy/NSPAppGroup.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using NSmartProxy.Data; + +namespace NSmartProxy +{ + /// + /// 按host区分的app组,ActivateApp始终是最后一个进来的app。 + /// 一个app组中所有的app的协议必须相同。 + /// + public class NSPAppGroup : Dictionary + { + public new void Add(string key, NSPApp value) + { + key = key.Replace(" ", ""); + _activateApp = value; + ProtocolInGroup = value.AppProtocol; + base.Add(key, value); + } + + public new NSPApp this[string key] + { + get => base[key.Replace(" ", "")]; + set + { + _activateApp = value; + ProtocolInGroup = value.AppProtocol; base[key.Replace(" ", "")] = value; + } + } + + public NSPApp _activateApp; + + public NSPApp ActivateApp + { + get { return _activateApp; } + // set { _activateApp = value; } + } + public Protocol ProtocolInGroup;//组协议 + + public new void Clear() + { + _activateApp = null; + base.Clear(); + } + + public bool IsAllClosed() + { + + foreach (var key in base.Keys) + { + if (!this[key].IsClosed) + { + return false; + } + } + return true; + } + + public TcpListener Listener { get; set; } + public UdpClient UdpClient { get; set; } + public Task UdpTransmissionTask { get; set; } + } +} diff --git a/src/NSmartProxy/NSPClient.cs b/src/NSmartProxy/NSPClient.cs index 4ff1ba5..be7ac6c 100644 --- a/src/NSmartProxy/NSPClient.cs +++ b/src/NSmartProxy/NSPClient.cs @@ -10,6 +10,8 @@ public class NSPClient public int ClientID; public DateTime LastUpdateTime; public TcpClient ConfigClient; //配置用的socket + + public Dictionary AppMap; //Appid->app public NSPClient() @@ -37,21 +39,37 @@ public int RegisterNewApp() return app.AppId; } + /// + /// 绑定主机头host到某个端口上,同一端口可以多次绑定 + /// + /// + /// + /// + //public int BindHost(string host, int port) + //{ + // if (AppMap[port].HttpApps.Count == 0) + // { + // AppMap. + // } + + // AppMap[port].HttpApps. + //} + public NSPApp GetApp(int appId) { return AppMap[appId]; } - public int Close() - { - //统计关闭的连接数 - int ClosedConnectionCount = 0; - foreach (var AppKV in AppMap) - { - ClosedConnectionCount += AppKV.Value.Close(); - } + //public int Close() + //{ + // //统计关闭的连接数 + // int ClosedConnectionCount = 0; + // foreach (var AppKV in AppMap) + // { + // ClosedConnectionCount += AppKV.Value.Close(); + // } - return ClosedConnectionCount; - } + // return ClosedConnectionCount; + //} } } \ No newline at end of file diff --git a/src/NSmartProxy/NSmartProxy.csproj b/src/NSmartProxy/NSmartProxy.csproj index 2a21b20..76b4bfe 100644 --- a/src/NSmartProxy/NSmartProxy.csproj +++ b/src/NSmartProxy/NSmartProxy.csproj @@ -2,26 +2,21 @@ Library - netcoreapp2.1 + net6.0 - + Always - - - - - - - - + + + diff --git a/src/NSmartProxy/Server.cs b/src/NSmartProxy/Server.cs index 1379321..8fabcf2 100644 --- a/src/NSmartProxy/Server.cs +++ b/src/NSmartProxy/Server.cs @@ -5,7 +5,9 @@ using System.Linq; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Timers; @@ -14,11 +16,13 @@ using NSmartProxy.Infrastructure; using NSmartProxy.Authorize; using NSmartProxy.Data.Config; -using NSmartProxy.Data.Entity; +using NSmartProxy.Data.DBEntities; using NSmartProxy.Interfaces; using NSmartProxy.Shared; using static NSmartProxy.Server; using NSmartProxy.Database; +using NSmartProxy.Infrastructure.Extension; +using System.Collections.Concurrent; namespace NSmartProxy { @@ -28,7 +32,7 @@ namespace NSmartProxy //| | //| +----------+ | +-----------+ //| | | | | | - //| | client |------------> provider | + //| | Client |------------> Provider | //| | | | | | //| +----+-----+ | +------^----+ //| | | | @@ -36,21 +40,22 @@ namespace NSmartProxy //| | | | //| +----V-----+ | | //| | | | | - //| | IIS | | | - //| | | | | - //| +----------+ | +------+-------+ - //| | | | - //| | | consumer | + //| | Target | | | + //| | Server | | | + //| | | | +------+-------+ + //| +----------+ | | | + //| | | Consumer | //| | | | //+------------------------+ +--------------+ public class Server { - public const string USER_DB_PATH = "./nsmart_user"; + public const string USER_DB_PATH = "./nsmart_user.db"; public const string SECURE_KEY_FILE_PATH = "./nsmart_sec_key"; protected ClientConnectionManager ConnectionManager = null; protected IDbOperator DbOp; protected NSPServerContext ServerContext; + private ConcurrentDictionary transferTokenDic = new ConcurrentDictionary(); internal static INSmartLogger Logger; //inject @@ -59,6 +64,7 @@ public Server(INSmartLogger logger) //initialize Logger = logger; ServerContext = new NSPServerContext(); + Global.Logger = logger; } /// @@ -72,8 +78,6 @@ public Server SetServerConfigPath(string configPath) //bad design return this; } - - public Server SetAnonymousLogin(bool isSupportAnonymous) { ServerContext.SupportAnonymousLogin = isSupportAnonymous; @@ -87,16 +91,11 @@ public Server SetConfiguration(NSPServerConfig config) return this; } - //必须设置远程端口才可以通信 //TODO 合并到配置里 - //public Server SetWebPort(int port) - //{ - // WebManagementPort = port; - // return this; - //} + //public static X509Certificate2 TestCert; public async Task Start() { - DbOp = new NSmartDbOperator(USER_DB_PATH, USER_DB_PATH + "_index");//加载数据库 + DbOp = new LiteDbOperator(USER_DB_PATH);//加载数据库 //从配置文件加载服务端配置 InitSecureKey(); TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; @@ -107,20 +106,24 @@ public async Task Start() //1.反向连接池配置 ConnectionManager = ClientConnectionManager.GetInstance().SetServerContext(ServerContext); //注册客户端发生连接时的事件 - ConnectionManager.AppTcpClientMapConfigConnected += ConnectionManager_AppAdded; - ConnectionManager.ListenServiceClient(DbOp); + ConnectionManager.AppTcpClientMapConfigApplied += ConnectionManager_AppAdded; + _ = ConnectionManager.ListenServiceClient(DbOp); Logger.Debug("NSmart server started"); //2.开启http服务 if (ServerContext.ServerConfig.WebAPIPort > 0) { - var httpServer = new HttpServer(Logger, DbOp, ServerContext); - httpServer.StartHttpService(ctsHttp, ServerContext.ServerConfig.WebAPIPort); + var httpServer = new HttpServer(Logger, DbOp, ServerContext, new HttpServerApis(ServerContext, DbOp, "./log")); + _ = httpServer.StartHttpService(ctsHttp, ServerContext.ServerConfig.WebAPIPort); } //3.开启心跳检测线程 - ProcessHeartbeatsCheck(Global.HeartbeatCheckInterval, ctsConsumer); + _ = ProcessHeartbeatsCheck(Global.HeartbeatCheckInterval, ctsConsumer); + //3.5 加载SSL证书 + Logger.Debug("SSL CA Generating..."); + ServerContext.InitCertificates(); + Logger.Debug("SSL CA Generated."); //4.开启配置服务(常开) try { @@ -177,7 +180,6 @@ private async Task ProcessHeartbeatsCheck(int interval, CancellationTokenSource finally { Logger.Debug("fatal error:心跳检测处理异常终止。"); - //TODO 重新开始 } } @@ -190,9 +192,9 @@ private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTask private async Task StartConfigService(CancellationTokenSource accepting) { - TcpListener listenerConfigService = new TcpListener(IPAddress.Any, ServerContext.ServerConfig.ConfigServicePort); + TcpListener listenerConfigService = new TcpListener(IPAddress.Any, ServerContext.ServerConfig.ConfigPort); - Logger.Debug("Listening config request on port " + ServerContext.ServerConfig.ConfigServicePort + "..."); + Logger.Debug("Listening config request on port " + ServerContext.ServerConfig.ConfigPort + "..."); var taskResultConfig = AcceptConfigRequest(listenerConfigService); await taskResultConfig; //block here to hold open the server @@ -200,58 +202,86 @@ private async Task StartConfigService(CancellationTokenSource accepting) } /// - /// 有连接连上则开始侦听新的端口 + /// 客户端连接时,会发送端口信息,此时服务端根据此信息 + /// 开始侦听新的端口 /// /// /// private void ConnectionManager_AppAdded(object sender, AppChangedEventArgs e) { - Server.Logger.Debug("AppTcpClientMapReverseConnected事件已触发"); + Server.Logger.Debug("AppTcpClientMapConfigConnected"); int port = 0; - foreach (var kv in ServerContext.PortAppMap) + Dictionary appMap = null; + if (e.App.AppProtocol == Protocol.UDP) { - if (kv.Value.AppId == e.App.AppId && - kv.Value.ClientId == e.App.ClientId) port = kv.Key; + appMap = ServerContext.UDPPortAppMap; + } + else + { + appMap = ServerContext.PortAppMap; + } + //多个app共用一个端口时,只需要对这个端口开启一次侦听循环 + foreach (var kv in appMap) + { + if (kv.Value.ActivateApp.AppId == e.App.AppId && + kv.Value.ActivateApp.ClientId == e.App.ClientId) + { + port = kv.Value.ActivateApp.ConsumePort; + break; + } } if (port == 0) throw new Exception("app未注册"); - var ct = new CancellationToken(); - ListenConsumeAsync(port); + _ = ListenConsumeAsync(port, appMap); } /// - /// 主循环,处理所有来自外部的请求 + /// 主循环,处理所有来自外部的请求(UDP&TCP) /// - /// - /// + /// + /// /// - async Task ListenConsumeAsync(int consumerPort) + async Task ListenConsumeAsync(int consumerPort, Dictionary appMap) { + //TODO 区分http请求 var cts = new CancellationTokenSource(); var ct = cts.Token; try { - var consumerlistener = new TcpListener(IPAddress.Any, consumerPort); - var nspApp = ServerContext.PortAppMap[consumerPort]; - - consumerlistener.Start(1000); - nspApp.Listener = consumerlistener; - nspApp.CancelListenSource = cts; - - //临时编下号,以便在日志里区分不同隧道的连接 - string clientApp = $"clientapp:{nspApp.ClientId}-{nspApp.AppId}"; - while (!ct.IsCancellationRequested) + var nspAppGrp = appMap[consumerPort]; + nspAppGrp.ActivateApp.CancelListenSource = cts; + if (nspAppGrp.ProtocolInGroup == Protocol.UDP) //UDP协议侦听 { - Logger.Debug("listening serviceClient....Port:" + consumerPort); - //I.主要对外侦听循环 - //Task.Factory.StartNew(consumerlistener.AcceptTcpClientAsync()) + var consumerUdpClient = new UdpClient(consumerPort); + nspAppGrp.UdpClient = consumerUdpClient; - var consumerClient = await consumerlistener.AcceptTcpClientAsync(); - ProcessConsumeRequestAsync(consumerPort, clientApp, consumerClient, ct); + //启动udp传输,外层仅处理服务端到客户端的请求 + while (!ct.IsCancellationRequested) + { + Logger.Debug("Server UDP Receiving....Port:" + consumerPort); + var receiveResult = await consumerUdpClient.ReceiveAsync(); + _ = ProcessConsumeUdpRequestAsync(consumerUdpClient, consumerPort, receiveResult, ct); + //int x = 1; + } + } + else //TCP HTTP/HTTPS都走tcplistener侦听 + { + var consumerTcpListener = new TcpListener(IPAddress.Any, consumerPort); + consumerTcpListener.Start(1000); + nspAppGrp.Listener = consumerTcpListener; + //临时编下号,以便在日志里区分不同隧道的连接 + while (!ct.IsCancellationRequested) + { + Logger.Debug("Server TCP listening....Port:" + consumerPort); + //I.主要对外侦听循环 + var consumerClient = await consumerTcpListener.AcceptTcpClientAsync(); + _ = ProcessConsumeTcpRequestAsync(consumerPort, consumerClient, ct);//同步发送 + } } } catch (ObjectDisposedException ode) { + _ = ode; Logger.Debug($"外网端口{consumerPort}侦听时被外部终止"); } catch (Exception ex) @@ -259,30 +289,229 @@ async Task ListenConsumeAsync(int consumerPort) Logger.Debug($"外网端口{consumerPort}侦听时出错{ex}"); } } + private object receiveUdpLocker = new object(); + ///接收到消费端的udp请求之后,开始侦测服务端->客户端的请求 + private async Task ProcessConsumeUdpRequestAsync(UdpClient consumerUdpClient, int consumerPort, + UdpReceiveResult receiveResult, + CancellationToken ct) + { + var nspAppGroup = ServerContext.UDPPortAppMap[consumerPort]; + if (nspAppGroup.ProtocolInGroup == Protocol.UDP) + { + try + { + var s2pClient = ConnectionManager.GetClientForUdp(consumerPort, null); + var tunnelStream = s2pClient.GetStream(); + var nspApp = nspAppGroup.ActivateApp; + // method ip(D) port buffer(D) + // udp X 2 X + tunnelStream.Write(new byte[] { (byte)ControlMethod.UDPTransfer }, 0, 1); + //tunnelStream.Write(StringUtil.IntTo2Bytes(nspApp.AppId)); + //tunnelStream.Write(StringUtil.IntTo2Bytes(receiveResult.Buffer.Length), 0, 2); + await tunnelStream.WriteDLengthBytes(receiveResult.RemoteEndPoint.Address.ToString()); + tunnelStream.Write(StringUtil.IntTo2Bytes(receiveResult.RemoteEndPoint.Port)); + await tunnelStream.WriteDLengthBytes(receiveResult.Buffer); + Logger.Debug($"UDP数据包已发送{receiveResult.Buffer.Length}字节,remote ep:{receiveResult.RemoteEndPoint.ToString()}"); + lock (receiveUdpLocker) + { + if (nspAppGroup.UdpTransmissionTask == null)//一个app只产生一个udp隧道(共用隧道) + { + //传回连接 (异步) + nspAppGroup.UdpTransmissionTask = OpenUdpTransmission(receiveResult.RemoteEndPoint, tunnelStream, nspAppGroup, ct); + } + //int x = 1; + } + } + catch (Exception ex) + { + Logger.Debug("UDP传输错误:" + ex.ToString()); + + } + + } + } - private async Task ProcessConsumeRequestAsync(int consumerPort, string clientApp, TcpClient consumerClient, CancellationToken ct) + /// + /// udp的传输流,只需要处理客户端到服务端的数据流 + /// + /// + /// + /// + /// + /// + /// + /// + private async Task OpenUdpTransmission(IPEndPoint FXXXconsumerEndPoint,//这个endpoint没有意义 + NetworkStream providerStream, NSPAppGroup nspAppGrp, CancellationToken ct) + { + try + { + //byte[] buffer = new byte[Global.ServerTunnelBufferSize]; + //int bytesRead; + var nspApp = nspAppGrp.ActivateApp; + var udpClient = nspAppGrp.UdpClient; + //var buffer = new byte[Global.ServerUdpBufferSize]; + + while (true) + { + //读取endpoint,必须保证如下数据包原子性 + //IPv4/IPv6(D) port returnbuffer(D) + //X 2 X + byte[] ipByte = await providerStream.ReadNextDLengthBytes();//read,(只会存活很短时间不是这个) + byte[] portByte = new byte[2]; + byte[] returnBuffer = null; + + await providerStream.ReadAsync(portByte, 0, 2); + returnBuffer = await providerStream.ReadNextDLengthBytes(); + + //if (readBytes > 0) + //{ + _ = udpClient.SendAsync(returnBuffer, + returnBuffer.Length, + new IPEndPoint(IPAddress.Parse(Encoding.ASCII.GetString(ipByte)), + StringUtil.DoubleBytesToInt(portByte))); + // } + } + } + catch (Exception ex) + { + Logger.Debug("udp隧道传输失败"); + Logger.Debug(ex.ToString()); + throw; + } + + + // } + //while ((bytesRead = + // await providerStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) + //{ + //TODO 8 读取封包 + //读取客户端传过来的封包:consumer源 EndPoint 目的 EndPoint + //new udpclient发送给相应的udpclient + //if (isCompress) + //{ + // var compressBuffer = StringUtil.DecompressInSnappy(buffer, 0, bytesRead); + // bytesRead = compressBuffer.Length; + + // await consumerUdpClient.SendAsync(compressBuffer, bytesRead, consumerEndPoint); + // //await toStream.WriteAsync(compressBuffer, 0, bytesRead, ct).ConfigureAwait(false); + //} + //else + //{ + // await consumerUdpClient.SendAsync(buffer, bytesRead, consumerEndPoint); + // //await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + //} + //ServerContext.TotalSentBytes += bytesRead; //上行 + //} + } + + + private async Task ProcessConsumeTcpRequestAsync(int consumerPort, TcpClient consumerClient, CancellationToken ct) { TcpTunnel tunnel = new TcpTunnel { ConsumerClient = consumerClient }; - ServerContext.PortAppMap[consumerPort].Tunnels.Add(tunnel); - //Logger.Debug("consumer已连接:" + consumerClient.Client.RemoteEndPoint.ToString()); - ServerContext.ConnectCount += 1; - //II.弹出先前已经准备好的socket - TcpClient s2pClient = await ConnectionManager.GetClient(consumerPort); - if (s2pClient == null) + var nspAppGroup = ServerContext.PortAppMap[consumerPort]; + NSPApp nspApp = null; + TcpClient s2pClient = null; + Stream consumerStream = consumerClient.GetStream(); + Stream providerStream = null; + byte[] restBytes = null; + + try { - Logger.Debug($"端口{consumerPort}获取反弹连接超时,关闭传输。"); + + if (nspAppGroup.ProtocolInGroup == Protocol.HTTP /*|| nspAppGroup.ProtocolInGroup == Protocol.HTTPS*/) + {//不论是http协议还是https协议,有证书就加密 + if (ServerContext.PortCertMap.TryGetValue(consumerPort.ToString(), out X509Certificate cert2)) + { + consumerStream = consumerStream.ProcessSSL(cert2); + } + var tp = await ReadHostName(consumerStream); + + if (tp == null) + { Server.Logger.Debug("未在请求中找到主机名"); return; } + + string host = tp.Item1; + restBytes = Encoding.UTF8.GetBytes(tp.Item2); //预发送bytes,因为这部分用来抓host消费掉了 + //restBytesLength = tp.Item3; + s2pClient = await ConnectionManager.GetClientForTcp(consumerPort, host); + if (nspAppGroup.ContainsKey(host)) + { + nspAppGroup[host].Tunnels.Add(tunnel); //bug修改:建立隧道 + } + else + { + Server.Logger.Debug($"不存在host为“{host}”的主机名,但访问端依然以此主机名访问。"); + } + + nspApp = nspAppGroup[host]; + } + else if (nspAppGroup.ProtocolInGroup == Protocol.TCP) //TCP + { + s2pClient = await ConnectionManager.GetClientForTcp(consumerPort); + nspAppGroup.ActivateApp.Tunnels.Add(tunnel);//bug修改:建立隧道 + nspApp = nspAppGroup.ActivateApp; + } + } + catch (TimeoutException ex) + { + Logger.Debug($"端口{consumerPort}获取反弹连接超时,关闭传输。=>" + ex.Message); //获取clientid //关闭本次连接 - ServerContext.CloseAllSourceByClient(ServerContext.PortAppMap[consumerPort].ClientId); + ServerContext.CloseAllSourceByClient(ServerContext.PortAppMap[consumerPort].ActivateApp.ClientId); + return; + } + catch (KeyNotFoundException ex) + { + Server.Logger.Debug("未绑定此host:=>" + ex.Message); + consumerClient.Close(); + return; } + ServerContext.ConnectCount += 1; + + //TODO 如果NSPApp中是http,则需要进一步分离,通过GetHTTPClient来分出对应的client以建立隧道 + //II.弹出先前已经准备好的socket tunnel.ClientServerClient = s2pClient; + CancellationTokenSource transfering = new CancellationTokenSource(); + transferTokenDic.TryAdd(transfering.GetHashCode(), transfering); + Server.Logger.Debug("记录一个连接的中断控制token:" + transfering.Token.GetHashCode().ToString()); //✳关键过程✳ //III.发送一个字节过去促使客户端建立转发隧道,至此隧道已打通 //客户端接收到此消息后,会另外分配一个备用连接 - s2pClient.GetStream().WriteAndFlushAsync(new byte[] { 0x01 }, 0, 1); - await TcpTransferAsync(consumerClient, s2pClient, clientApp, ct); + //TODO 4 增加一个udp转发的选项 + providerStream = s2pClient.GetStream(); + //TODO 5 这里会出错导致无法和客户端通信 + try + { + var bytes = new List { (byte)ControlMethod.TCPTransfer }; + bytes.AddRange(BitConverter.GetBytes(transfering.GetHashCode())); + //向客户端发送一个主动建立TCP连接的标记 + await providerStream.WriteAndFlushAsync(bytes.ToArray(), 0, 5);//双端标记S0001 + } + catch + { + Logger.Debug($"client:{nspApp.ClientId} app:{nspApp.AppId}写入失败,尝试切断该客户端"); + ServerContext.CloseAllSourceByClient(nspApp.ClientId); + return; + } + + //预发送bytes,因为这部分用来抓host消费掉了,所以直接转发 + if (restBytes != null) + await providerStream.WriteAsync(restBytes, 0, restBytes.Length, transfering.Token); + + //TODO 4 TCP则打通隧道进行转发,UDP直接转发 + // if tcp + try + { + await OpenTcpTransmission(consumerStream, providerStream, nspApp, transfering.Token); + } + finally + { + consumerClient.Close(); + s2pClient.Close(); + transfering.Cancel(); + } } #region 配置连接相关 @@ -301,7 +530,7 @@ private async Task AcceptConfigRequest(TcpListener listenerConfigService) while (true) { var client = await listenerConfigService.AcceptTcpClientAsync(); - ProcessConfigRequestAsync(client); + _ = ProcessConfigRequestAsync(client); } } @@ -327,25 +556,25 @@ private async Task ProcessConfigRequestAsync(TcpClient client) return; } - Protocol proto = (Protocol)protoRequestBytes[0]; -#if DEBUG - //Server.Logger.Debug("appRequestBytes received."); -#endif + ServerProtocol proto = (ServerProtocol)protoRequestBytes[0]; switch (proto) { - case Protocol.ClientNewAppRequest: + case ServerProtocol.ClientNewAppRequest: await ProcessAppRequestProtocol(client); break; - case Protocol.Heartbeat: + case ServerProtocol.Heartbeat: await ProcessHeartbeatProtocol(client); break; - case Protocol.CloseClient: + case ServerProtocol.CloseClient: await ProcessCloseClientProtocol(client); break; - case Protocol.Reconnect: + case ServerProtocol.Reconnect: await ProcessAppRequestProtocol(client, true); break; + case ServerProtocol.Disconnet: + await ProcessDisconnetClientProtocol(client); + break; default: throw new Exception("接收到异常请求。"); } @@ -359,6 +588,32 @@ private async Task ProcessConfigRequestAsync(TcpClient client) } + private async Task ProcessDisconnetClientProtocol(TcpClient client) + { + Server.Logger.Debug("Now processing Disconnet Client protocol...."); + NetworkStream nstream = client.GetStream(); + byte[] appRequestBytes = new byte[4]; + int resultByte = await nstream.ReadAsync(appRequestBytes, 0, appRequestBytes.Length, Global.DefaultConnectTimeout); + //Server.Logger.Debug("appRequestBytes received."); + if (resultByte < 1) + { + Server.Logger.Debug("服务端read失败,关闭连接"); + client.Client.Close(); + return; + } + int tokenId = BitConverter.ToInt32(appRequestBytes, 0); + try + { + transferTokenDic[tokenId].Cancel(); + Server.Logger.Debug($"执行断开用户链接{tokenId}成功。"); + } + catch (Exception ex) + { + Server.Logger.Error($"执行断开用户链接{tokenId}失败!!!", ex); + } + client.Close(); + } + private async Task ProcessCloseClientProtocol(TcpClient client) { Server.Logger.Debug("Now processing CloseClient protocol...."); @@ -389,59 +644,83 @@ private async Task ProcessHeartbeatProtocol(TcpClient client) int heartBeatLength = 2; byte[] appRequestBytes = new byte[heartBeatLength]; int resultByte = await nstream.ReadAsync(appRequestBytes, 0, appRequestBytes.Length, Global.DefaultConnectTimeout); - //Server.Logger.Debug("appRequestBytes received."); if (resultByte < 1) { CloseClient(client); return; } - //1.2 响应ACK - await nstream.WriteAndFlushAsync(new byte[] { 0x01 }, 0, 1); + int clientID = StringUtil.DoubleBytesToInt(appRequestBytes[0], appRequestBytes[1]); -#if DEBUG - //Server.Logger.Debug($"Now processing {clientID}'s Heartbeat protocol...."); -#endif ServerContext.ClientConnectCount += 1; //2.更新最后更新时间 if (ServerContext.Clients.ContainsKey(clientID)) { - ServerContext.Clients[clientID].LastUpdateTime = DateTime.Now; + //2.2 响应ACK + await nstream.WriteAndFlushAsync(new byte[] { (byte)ControlMethod.TCPTransfer }); + + var nspClient = ServerContext.Clients[clientID]; + nspClient.LastUpdateTime = DateTime.Now; + + //2.3 TODO 5 发送保活数据包,一旦建立了这种机制就不需要iocontrol的keepalive了 + try + { + //Server.Logger.Debug($"尝试对{clientID}发送保活数据包..");//TODO 待删除 + foreach (var kv in nspClient.AppMap) + { + TcpClient peekedClient = kv.Value.TcpClientBlocks.Peek(); + if (peekedClient != null) + { + //发送保活数据 + await peekedClient.GetStream().WriteAndFlushAsync(new byte[] { (byte)ControlMethod.KeepAlive }); + } + } + } + catch (Exception ex) + { + Logger.Debug($"{clientID}保活失败尝试切断连接"); + throw ex; + } + } else { Server.Logger.Debug($"clientId为{clientID}客户端已经被清除。"); } - //3.接收完立即关闭 + + //3.接收完立即关闭,心跳协议无需使用nagle算法 + client.NoDelay = true; client.Close(); } private async Task ProcessAppRequestProtocol(TcpClient client, bool IsReconnect = false) { Server.Logger.Debug("Now processing request protocol...."); + Logger.Debug("The client ip address is:" + client.Client.RemoteEndPoint.ToString()); NetworkStream nstream = client.GetStream(); int clientIdFromToken = 0; //1.读取配置请求1 - //如果是重连请求,则读取接下来5个字符,清 - //空服务端所有与该client相关的所有连接配置 - - //TODO !!!!兼容原有的重连逻辑 - //TODO !!!!获取Token,截取clientID,校验 //TODO !!!!这里的校验逻辑和httpserver_api存在太多重复,需要重构 clientIdFromToken = await GetClientIdFromNextTokenBytes(client); - //var userClaims = StringUtil.ConvertStringToTokenClaims(clientIdFromToken); if (clientIdFromToken == 0) { + //TODO 2 服务端错误,校验失败 + await nstream.WriteAsync(new byte[] { (byte)ServerStatus.AuthFailed }); + client.Close(); + return false; + } + else if (clientIdFromToken == -1) + { + //用户被禁用 + await nstream.WriteAsync(new byte[] { (byte)ServerStatus.UserBanned }); client.Close(); return false; } - //if (IsReconnect) 因为加入了始终校验的机制,取消重连规则 - //{ - ServerContext.CloseAllSourceByClient(clientIdFromToken); - // } + //强制下线其他客户端 + ServerContext.CloseAllSourceByClient(clientIdFromToken, isForceClose: true); //1.3 获取客户端请求数 int configRequestLength = 3; @@ -458,11 +737,15 @@ private async Task ProcessAppRequestProtocol(TcpClient client, bool IsReco //2.根据配置请求1获取更多配置信息 int appCount = (int)appRequestBytes[2]; - byte[] consumerPortBytes = new byte[appCount * 2]; - int resultByte2 = await nstream.ReadAsyncEx(consumerPortBytes); + //port proto option(iscompress) host description + //2 1 1 1024 96 + byte[] consumerPortBytes = new byte[appCount * (2 + 1 + 1 + 1024 + 96)];//TODO 2 暂时这么写,亟需修改 + int resultByte2 = await nstream.ReadNextSTLengthBytes(consumerPortBytes); + // int resultBytetest = await nstream.ReadAsyncEx(consumerPortBytes); //Server.Logger.Debug("consumerPortBytes received."); if (resultByte2 < 1) { + Server.Logger.Debug("定长包没有正确接收"); CloseClient(client); return true; } @@ -477,6 +760,8 @@ private async Task ProcessAppRequestProtocol(TcpClient client, bool IsReco } catch (Exception ex) { + await nstream.WriteAsync(new byte[] { (byte)ServerStatus.UnknowndFailed }); + //TODO 2 服务端错误:未知错误 Logger.Debug(ex.ToString()); } finally @@ -493,7 +778,7 @@ private async Task ProcessAppRequestProtocol(TcpClient client, bool IsReco /// /// 通过token获取clientid - /// 返回0说明失败 + /// 返回0说明失败,返回-1说明用户被禁 /// /// /// @@ -538,7 +823,7 @@ private async Task GetClientIdFromNextTokenBytes(TcpClient client) if (ServerContext.ServerConfig.BoundConfig.UsersBanlist.Contains(userId)) { Server.Logger.Debug("用户被禁用"); - return 0; + return -1; } else { @@ -554,28 +839,21 @@ private async Task GetClientIdFromNextTokenBytes(TcpClient client) #region datatransfer //3端互相传输数据 - async Task TcpTransferAsync(TcpClient consumerClient, TcpClient providerClient, - string clientApp, + async Task OpenTcpTransmission(Stream consumerStream, Stream providerStream, + NSPApp nspApp, CancellationToken ct) { try { - Server.Logger.Debug($"New client ({clientApp}) connected"); + Server.Logger.Debug($"New client ({nspApp.ClientId}-{nspApp.AppId}) connected"); - CancellationTokenSource transfering = new CancellationTokenSource(); - - var providerStream = providerClient.GetStream(); - var consumerStream = consumerClient.GetStream(); - Task taskC2PLooping = ToStaticTransfer(transfering.Token, consumerStream, providerStream, clientApp); - Task taskP2CLooping = StreamTransfer(transfering.Token, providerStream, consumerStream, clientApp); + Task taskC2PLooping = ToStaticTransfer(ct, consumerStream, providerStream, nspApp); + Task taskP2CLooping = StreamTransfer(ct, providerStream, consumerStream, nspApp); //任何一端传输中断或者故障,则关闭所有连接 var comletedTask = await Task.WhenAny(taskC2PLooping, taskP2CLooping); - //comletedTask. - Logger.Debug($"Transferring ({clientApp}) STOPPED"); - consumerClient.Close(); - providerClient.Close(); - transfering.Cancel(); + Logger.Debug($"Transferring ({nspApp.ClientId}-{nspApp.AppId}) STOPPED"); + } catch (Exception e) { @@ -585,65 +863,129 @@ async Task TcpTransferAsync(TcpClient consumerClient, TcpClient providerClient, } - private async Task StreamTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, string clientApp) + private async Task StreamTransfer(CancellationToken ct, Stream fromStream, Stream toStream, NSPApp nspApp) { using (fromStream) { - byte[] buffer = new byte[81920]; - int bytesRead; + byte[] buffer = new byte[Global.ServerTunnelBufferSize]; try { - while ((bytesRead = - await fromStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) + int bytesRead; + if (nspApp.IsCompress) + { + while (!ct.IsCancellationRequested) + { + //Array.Resize(array: ref buffer, newSize: bytesRead);//此处存在copy,需要看看snappy是否支持偏移量数组 + byte[] bufferCompressed = await fromStream.ReadNextQLengthBytes(); + if (bufferCompressed.Length == 0) break; + var compressBuffer = StringUtil.DecompressInSnappy(bufferCompressed,0,bufferCompressed.Length); + bytesRead = compressBuffer.Length; + await toStream.WriteAsync(compressBuffer, 0, bytesRead, ct).ConfigureAwait(false); + } + } + else { - await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); - ServerContext.TotalSentBytes += bytesRead; //上行 + while ((bytesRead = + await fromStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) + { + + await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + + ServerContext.TotalSentBytes += bytesRead; //上行 + } } } - catch (IOException ioe) + catch (Exception ioe) { - //Server.Logger.Info(ioe.Message); + if (ioe is IOException) { return; } //Suppress this exception. + throw; } } - //Server.Logger.Debug($"{clientApp}对服务端传输关闭。"); } - private async Task ToStaticTransfer(CancellationToken ct, NetworkStream fromStream, NetworkStream toStream, string clientApp) + private async Task ToStaticTransfer(CancellationToken ct, Stream fromStream, Stream toStream, NSPApp nspApp) { using (fromStream) { - byte[] buffer = new byte[81920]; - int bytesRead; + byte[] buffer = new byte[Global.ServerTunnelBufferSize]; try { + int bytesRead; while ((bytesRead = await fromStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false)) != 0) { - await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + if (nspApp.IsCompress) + { + //Array.Resize(array: ref buffer, newSize: bytesRead);//此处存在copy,需要看看snappy是否支持偏移量数组 + var compressInSnappy = StringUtil.CompressInSnappy(buffer,0,bytesRead); + //var compressedBuffer = compressInSnappy.ContentBytes; + // bytesRead = compressInSnappy.Length; + //TODO 封包传送 + if (ct.IsCancellationRequested) { Global.Logger.Info("=传输外部中止="); return; } + await toStream.WriteQLengthBytes(compressInSnappy.ContentBytes, compressInSnappy.Length).ConfigureAwait(false); + } + else + { + await toStream.WriteAsync(buffer, 0, bytesRead, ct).ConfigureAwait(false); + } ServerContext.TotalReceivedBytes += bytesRead; //下行 } } - catch (IOException ioe) + catch (Exception ioe) { - //Server.Logger.Info(ioe.Message); + if (ioe is IOException) { return; } //Suppress this exception. + throw; } - - } - //Server.Logger.Debug($"{clientApp}对客户端传输关闭。"); } private void CloseClient(TcpClient client) { - Logger.Debug("invalid request,Closing client:" + client.Client.RemoteEndPoint.ToString()); - client.Close(); - Logger.Debug("Closed client:" + client.Client.RemoteEndPoint.ToString()); + if (client.Connected) + { + string remote = "unknown"; + try + { + remote = client.Client.RemoteEndPoint.ToString(); + } + catch (Exception ex) + { + Logger.Error("get client remote address fail:", ex); + } + Logger.Debug("invalid request,Closing client:" + remote); + client.Close(); + Logger.Debug("Closed client:" + remote); + } } - #endregion + #region http + private async Task> ReadHostName(Stream consumerStream) + { + //需要进一步截包 TODO 2 待优化1.内存优化 2.查询优化 + const int BUFFER_SIZE = 1024 * 1024 * 2; + var length = 0; + var data = string.Empty; + var bytes = new byte[BUFFER_SIZE]; + + do + {//item2:data item1:host + length = await consumerStream.ReadAsync(bytes, 0, BUFFER_SIZE); + data += Encoding.UTF8.GetString(bytes, 0, length); + } while (length > 0 && !data.Contains("\r\n\r\n")); + + if (length == 0) return null; + Regex reg = new Regex("\r\nhost: (.*?)\r\n"); + + return new Tuple( + reg.Match(data.ToLower()).Groups[1].Value,//需要进一步优化,使用list + data + + ); + } + #endregion } } \ No newline at end of file diff --git a/src/NSmartProxy/ServerModels.cs b/src/NSmartProxy/ServerModels.cs index 1878370..4ceea4b 100644 --- a/src/NSmartProxy/ServerModels.cs +++ b/src/NSmartProxy/ServerModels.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; @@ -13,10 +14,12 @@ public struct ClientIDAppID public int AppID; } + /// + /// nspclient集合 clientid->NSPClient + /// public class NSPClientCollection:IEnumerable { - //clientid->NSPClient - private Dictionary ClientMap; + private ConcurrentDictionary ClientMap; public NSPClient this[int index] { @@ -25,7 +28,7 @@ public NSPClient this[int index] } public NSPClientCollection() { - ClientMap = new Dictionary(); + ClientMap = new ConcurrentDictionary(); } public bool ContainsKey(int key) @@ -35,21 +38,21 @@ public bool ContainsKey(int key) public void RegisterNewClient(int key) { - if (!ClientMap.ContainsKey(key)) - ClientMap[key] = new NSPClient() + + ClientMap.TryAdd(key,new NSPClient() { ClientID = key, LastUpdateTime = DateTime.Now - }; + }); } - public int UnRegisterClient(int key) + public void UnRegisterClient(int key) { //关闭所有连接 - int closedClients = ClientMap[key].Close(); - this.ClientMap.Remove(key); + //int closedClients = ClientMap[key].Close(); + this.ClientMap.TryRemove(key,out _); //停止端口侦听 - return closedClients; + //return closedClients; } public IEnumerator GetEnumerator() diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/Chart.min.js b/src/NSmartProxy/Web/Chart.min.js similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/Chart.min.js rename to src/NSmartProxy/Web/Chart.min.js diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/NSmartProxyNew.png b/src/NSmartProxy/Web/NSmartProxyNew.png similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/NSmartProxyNew.png rename to src/NSmartProxy/Web/NSmartProxyNew.png diff --git a/src/NSmartProxy/Web/Web.config b/src/NSmartProxy/Web/Web.config new file mode 100644 index 0000000..b757205 --- /dev/null +++ b/src/NSmartProxy/Web/Web.config @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/bootstrap-table-expandable.js b/src/NSmartProxy/Web/bootstrap-table-expandable.js similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/bootstrap-table-expandable.js rename to src/NSmartProxy/Web/bootstrap-table-expandable.js diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/bootstrap.bundle.min.js b/src/NSmartProxy/Web/bootstrap.bundle.min.js similarity index 99% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/bootstrap.bundle.min.js rename to src/NSmartProxy/Web/bootstrap.bundle.min.js index 4320368..6a8b660 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/bootstrap.bundle.min.js +++ b/src/NSmartProxy/Web/bootstrap.bundle.min.js @@ -4,4 +4,3 @@ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],e):e((t=t||self).bootstrap={},t.jQuery)}(this,function(t,p){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)p(this._element).one(q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=n=i.clientWidth&&n>=i.clientHeight}),h=0l[t]&&!i.escapeWithReference&&(n=Math.min(h[e],l[t]-("right"===t?h.width:h.height))),Kt({},e,n)}};return c.forEach(function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";h=Qt({},h,u[e](t))}),t.offsets.popper=h,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,s=-1!==["top","bottom"].indexOf(o),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]r(i[a])&&(t.offsets.popper[l]=r(i[a])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!fe(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,s=r.popper,a=r.reference,l=-1!==["left","right"].indexOf(o),c=l?"height":"width",h=l?"Top":"Left",u=h.toLowerCase(),f=l?"left":"top",d=l?"bottom":"right",p=Zt(i)[c];a[d]-ps[d]&&(t.offsets.popper[u]+=a[u]+p-s[d]),t.offsets.popper=Vt(t.offsets.popper);var m=a[u]+a[c]/2-p/2,g=Nt(t.instance.popper),_=parseFloat(g["margin"+h],10),v=parseFloat(g["border"+h+"Width"],10),y=m-t.offsets.popper[u]-_-v;return y=Math.max(Math.min(s[c]-p,y),0),t.arrowElement=i,t.offsets.arrow=(Kt(n={},u,Math.round(y)),Kt(n,f,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(p,m){if(oe(p.instance.modifiers,"inner"))return p;if(p.flipped&&p.placement===p.originalPlacement)return p;var g=Gt(p.instance.popper,p.instance.reference,m.padding,m.boundariesElement,p.positionFixed),_=p.placement.split("-")[0],v=te(_),y=p.placement.split("-")[1]||"",E=[];switch(m.behavior){case ge:E=[_,v];break;case _e:E=me(_);break;case ve:E=me(_,!0);break;default:E=m.behavior}return E.forEach(function(t,e){if(_!==t||E.length===e+1)return p;_=p.placement.split("-")[0],v=te(_);var n,i=p.offsets.popper,o=p.offsets.reference,r=Math.floor,s="left"===_&&r(i.right)>r(o.left)||"right"===_&&r(i.left)r(o.top)||"bottom"===_&&r(i.top)r(g.right),c=r(i.top)r(g.bottom),u="left"===_&&a||"right"===_&&l||"top"===_&&c||"bottom"===_&&h,f=-1!==["top","bottom"].indexOf(_),d=!!m.flipVariations&&(f&&"start"===y&&a||f&&"end"===y&&l||!f&&"start"===y&&c||!f&&"end"===y&&h);(s||u||d)&&(p.flipped=!0,(s||u)&&(_=E[e+1]),d&&(y="end"===(n=y)?"start":"start"===n?"end":n),p.placement=_+(y?"-"+y:""),p.offsets.popper=Qt({},p.offsets.popper,ee(p.instance.popper,p.offsets.reference,p.placement)),p=ie(p.instance.modifiers,p,"flip"))}),p},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return o[s?"left":"top"]=r[n]-(a?o[s?"width":"height"]:0),t.placement=te(e),t.offsets.popper=Vt(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!fe(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=ne(t.instance.modifiers,function(t){return"preventOverflow"===t.name}).boundaries;if(e.bottomn.right||e.top>n.bottom||e.rightdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:vn},Ln="show",xn="out",Pn={HIDE:"hide"+Tn,HIDDEN:"hidden"+Tn,SHOW:"show"+Tn,SHOWN:"shown"+Tn,INSERTED:"inserted"+Tn,CLICK:"click"+Tn,FOCUSIN:"focusin"+Tn,FOCUSOUT:"focusout"+Tn,MOUSEENTER:"mouseenter"+Tn,MOUSELEAVE:"mouseleave"+Tn},Hn="fade",jn="show",Rn=".tooltip-inner",Fn=".arrow",Mn="hover",Wn="focus",Un="click",Bn="manual",qn=function(){function i(t,e){if("undefined"==typeof be)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=p(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(p(this.getTipElement()).hasClass(jn))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),p.removeData(this.element,this.constructor.DATA_KEY),p(this.element).off(this.constructor.EVENT_KEY),p(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&p(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===p(this.element).css("display"))throw new Error("Please use show on visible elements");var t=p.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){p(this.element).trigger(t);var n=m.findShadowRoot(this.element),i=p.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=m.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&p(o).addClass(Hn);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();p(o).data(this.constructor.DATA_KEY,this),p.contains(this.element.ownerDocument.documentElement,this.tip)||p(o).appendTo(l),p(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new be(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Fn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),p(o).addClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().on("mouseover",null,p.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,p(e.element).trigger(e.constructor.Event.SHOWN),t===xn&&e._leave(null,e)};if(p(this.tip).hasClass(Hn)){var h=m.getTransitionDurationFromElement(this.tip);p(this.tip).one(m.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=p.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==Ln&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),p(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(p(this.element).trigger(i),!i.isDefaultPrevented()){if(p(n).removeClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().off("mouseover",null,p.noop),this._activeTrigger[Un]=!1,this._activeTrigger[Wn]=!1,this._activeTrigger[Mn]=!1,p(this.tip).hasClass(Hn)){var r=m.getTransitionDurationFromElement(n);p(n).one(m.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){p(this.getTipElement()).addClass(Dn+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(p(t.querySelectorAll(Rn)),this.getTitle()),p(t).removeClass(Hn+" "+jn)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=bn(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?p(e).parent().is(t)||t.empty().append(e):t.text(p(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:m.isElement(this.config.container)?p(this.config.container):p(document).find(this.config.container)},t._getAttachment=function(t){return Nn[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)p(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Bn){var e=t===Mn?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Mn?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;p(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),p(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Wn:Mn]=!0),p(e.getTipElement()).hasClass(jn)||e._hoverState===Ln?e._hoverState=Ln:(clearTimeout(e._timeout),e._hoverState=Ln,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Ln&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Wn:Mn]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=xn,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===xn&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=p(this.element).data();return Object.keys(e).forEach(function(t){-1!==An.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),m.typeCheckConfig(wn,t,this.constructor.DefaultType),t.sanitize&&(t.template=bn(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(In);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(p(t).removeClass(Hn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=p(this).data(Cn),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),p(this).data(Cn,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return kn}},{key:"NAME",get:function(){return wn}},{key:"DATA_KEY",get:function(){return Cn}},{key:"Event",get:function(){return Pn}},{key:"EVENT_KEY",get:function(){return Tn}},{key:"DefaultType",get:function(){return On}}]),i}();p.fn[wn]=qn._jQueryInterface,p.fn[wn].Constructor=qn,p.fn[wn].noConflict=function(){return p.fn[wn]=Sn,qn._jQueryInterface};var Kn="popover",Qn="bs.popover",Vn="."+Qn,Yn=p.fn[Kn],zn="bs-popover",Xn=new RegExp("(^|\\s)"+zn+"\\S+","g"),Gn=l({},qn.Default,{placement:"right",trigger:"click",content:"",template:''}),$n=l({},qn.DefaultType,{content:"(string|element|function)"}),Jn="fade",Zn="show",ti=".popover-header",ei=".popover-body",ni={HIDE:"hide"+Vn,HIDDEN:"hidden"+Vn,SHOW:"show"+Vn,SHOWN:"shown"+Vn,INSERTED:"inserted"+Vn,CLICK:"click"+Vn,FOCUSIN:"focusin"+Vn,FOCUSOUT:"focusout"+Vn,MOUSEENTER:"mouseenter"+Vn,MOUSELEAVE:"mouseleave"+Vn},ii=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){p(this.getTipElement()).addClass(zn+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},o.setContent=function(){var t=p(this.getTipElement());this.setElementContent(t.find(ti),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ei),e),t.removeClass(Jn+" "+Zn)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(Xn);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t +

证书管理

+
+
+ +
+ +
+ + +
+
+ +
+ + +
+ +
+ +
+ +
+ + +
+ + + +
+ + +
+
+ +
+
+
+ +
+ + +
绑定端口:{Port}
+
Pfx证书
+

+ 创建时间:{CreateTime}
+ + 有效期限:{ToTime}
+ {Ext} +

+ 删除证书 +
+
+
+
+ +
+

其他配置

+
+
+ +
+
+
+
+ + + +
+ diff --git a/src/NSmartProxy/Web/config.js b/src/NSmartProxy/Web/config.js new file mode 100644 index 0000000..65e9a91 --- /dev/null +++ b/src/NSmartProxy/Web/config.js @@ -0,0 +1,185 @@ +//@ sourceURL= config.js +function getConfig(key, func) { + $.get(basepath + "GetConfig?key=" + key, function (res) { + + $("#config" + key).prop("checked", res.Data == "1"); + + }); +} + +$(document).ready(function () { + getConfig("AllowAnonymousUser"); + $("#frmCAConfig").attr("action", basepath + "/UploadTempFile"); + getAllCA(); +}); + +function toggleConfig(obj) { + var cbx = $(obj).find("input[type='checkbox']"); + var key = cbx.attr("id").replace("config", ""); + var value = cbx.prop("checked") == true ? "1" : "0"; + if (window.conDelayTask) window.clearTimeout(conDelayTask); + window.conDelayTask = window.setTimeout('setConfig("' + key + '","' + value + '")', 10);//hack +} + +function setConfig(key, value) { + $.get(basepath + "SetConfig?key=" + key + "&value=" + value, function (res) { + //$("#config" + key).prop("checked", res.Data == "1"); + console.log("设置成功"); + }); +} + +//文件上传 +function fileSelected() { + var file = document.getElementById('fileToUpload').files[0]; + if (file) { + var fileSize = 0; + if (file.size > 1024 * 1024) + fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB'; + else + fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB'; + document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize; + } + uploadFile(); + +} +function uploadFile() { + var fd = new FormData(); + fd.append("fileToUpload", document.getElementById('fileToUpload').files[0]); + var xhr = new XMLHttpRequest(); + xhr.upload.addEventListener("progress", uploadProgress, false); + xhr.addEventListener("load", uploadComplete, false); + xhr.addEventListener("error", uploadFailed, false); + xhr.addEventListener("abort", uploadCanceled, false); + xhr.open("POST", basepath + "/UploadTempFile");//修改成自己的接口 xhr.send(fd); + xhr.send(fd); + $("#divLoading").show(); +} +function uploadProgress(evt) { + if (evt.lengthComputable) { + var percentComplete = Math.round(evt.loaded * 100 / evt.total); + document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%'; + } + else { + document.getElementById('progressNumber').innerHTML = 'unable to compute'; + } +} +function uploadComplete(evt) { + /* 服务器端返回响应时候触发event事件*/ + var result = JSON.parse(evt.target.responseText); + if (result.State == 1) { + //显示文件名 + $("#fileInfo").show(); + $("#fileName").html("文件已上传"); + $("#fileBottoms").hide(); + $("#divLoading").hide(); + $("#fileToUpload").val(""); + $("#fileSize").html(""); + $("#progressNumber").html(""); + $("#hidCAFilename").val(result.Data); + //$("#fileName").html(result.data); + } +} +function uploadFailed(evt) { + alert("上传失败\n(There was an error attempting to upload the file.)"); +} +function uploadCanceled(evt) { + alert("客户端中断了上传。\n(The upload has been canceled by the user or the browser dropped the connection.)"); +} + +function delCABound(port) { + $.get(basepath + "DelCABound?port=" + port, function (res) { + getAllCA(); + }); +} + +function delCAFile() { + var filename = $("#hidCAFilename").val(); + $.get(basepath + "DelCAFile?filename=" + filename, function (res) { + if (res.State == 1) { + //getAllCA(); + $("#fileToUpload").val(""); + $("#fileInfo").hide(); + $("#fileBottoms").show(); + $("#divLoading").hide(); + } else { + alert('操作失败'); + } + }); + +} + +function addCABound() { + var port = $("#tbxPort").val(); + var filename = $("#hidCAFilename").val(); + var msg = ""; + if (port == undefined || port == "") + msg += "端口无效\n"; + if (filename == undefined || filename == "") + msg += "请上传或生成证书\n"; + + if (msg != "") { + alert(msg); + return; + } + $.get(basepath + "AddCABound?port=" + port + "&filename=" + filename, + function (res) { + getAllCA(); + if (res.State == 1) { + alert("证书绑定成功"); + clearCertForm(); + $("#divAddCert").collapse("hide"); + } else { + alert("服务端错误:" + res.Data); + } + + }); +} + +function genCA() { + var hosts = prompt("请输入绑定域名,多个域名请用逗号分隔。\n(eg.: *.tmoonlight.com,cloud.kd.com)"); + if (hosts && hosts.trim() != "") { + $("#divLoading").show(); + $.get(basepath + "GenerateCA?hosts=" + hosts, + function (res) { + $("#divLoading").hide(); + $("#fileInfo").show(); + $("#fileBottoms").hide(); + $("#fileName").html("证书已生成"); + $("#hidCAFilename").val(res.Data); + }); + } +} +var html = $("#divCertList").html();//第一次读取做为模板 +function getAllCA() { + //var html = $("#divCertList").html(); + + $.get(basepath + "GetAllCA", + function (res) { + var certListHtml = ""; + var data = res.Data; + for (var i = 0; i < data.length; i++) { + var htmlItem = html.replace("{Port}", data[i].Port).replace("{Port}", data[i].Port) + .replace("{CreateTime}", data[i].CreateTime) + .replace("{Hosts}", data[i].Hosts) + .replace("{Ext}", data[i].Extensions) + .replace("{ToTime}", data[i].ToTime); + certListHtml += htmlItem; + + } + $("#divCertList").html(certListHtml); + $("#divAddCert").collapse('hide'); + }); + +} + +function openCABound() { + $("#divAddCert").collapse('toggle'); +} + +function clearCertForm() { + $("#tbxPort").val(""); + $("#fileToUpload").val(""); + $("#fileInfo").hide(); + $("#fileBottoms").show(); + $("#divLoading").hide(); +} \ No newline at end of file diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/connections.html b/src/NSmartProxy/Web/connections.html similarity index 91% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/connections.html rename to src/NSmartProxy/Web/connections.html index 5f82901..2d6c2f4 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/connections.html +++ b/src/NSmartProxy/Web/connections.html @@ -15,10 +15,11 @@

连接

- - + + + @@ -32,6 +33,7 @@

连接

+ - + + diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/connections.js b/src/NSmartProxy/Web/connections.js similarity index 90% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/connections.js rename to src/NSmartProxy/Web/connections.js index f0bafaa..dce7fe2 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/connections.js +++ b/src/NSmartProxy/Web/connections.js @@ -66,11 +66,12 @@ for (i in clientsInfo) { var co = clientsInfo[i]; var statusHtml = ""; - statusHtml = (co.blocksCount == "1") ? htmlGood: htmlBroken; + statusHtml = (co.blocksCount == "1") ? htmlGood : htmlBroken; html += " " + - " " + + " " + "" + "" + + "" + "" + "" + "" + @@ -80,16 +81,16 @@ var tunnel = co.tunnels[j]; if (tunnel.consumerClient == undefined) tunnel.consumerClient = "已断开"; if (tunnel.clientServerClient == undefined) tunnel.clientServerClient = "已断开"; - html += "外网:" + tunnel.consumerClient+" "; + html += "外网:" + tunnel.consumerClient + " "; html += "内网:" + tunnel.clientServerClient; - html+="
" + html += "
"; } - - - html+="" + + + + html += "" + ""; //alert(clientObj.); - // clientObj.Cli + // clientObj.Cli } $("#connections_tb_body").html(html); //updatedClientsInfo(clientsInfo); @@ -98,7 +99,7 @@ } $(document).ready(function () { - + getClientsInfo(); }); $("#btnExpandAll").click(function () { expandAll(); }); diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.css b/src/NSmartProxy/Web/dashboard.css similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.css rename to src/NSmartProxy/Web/dashboard.css diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.html b/src/NSmartProxy/Web/dashboard.html similarity index 99% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.html rename to src/NSmartProxy/Web/dashboard.html index fd31010..53d328d 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.html +++ b/src/NSmartProxy/Web/dashboard.html @@ -21,7 +21,6 @@

仪表盘

-

最新日志

diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.js b/src/NSmartProxy/Web/dashboard.js similarity index 80% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.js rename to src/NSmartProxy/Web/dashboard.js index 34c3859..31b7292 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/dashboard.js +++ b/src/NSmartProxy/Web/dashboard.js @@ -36,12 +36,20 @@ function updatedServerStatus(data) { - + myChart2.data.datasets[0].data[0] = data.totalReceivedBytes; myChart2.data.datasets[0].data[1] = data.totalSentBytes; //myChart.data.datasets[0].data[2] = co; myChart2.update(); - myChart2.lab + } + + function updatedUserStatus(data) { + + myChart3.data.datasets[0].data[0] = data.onlineUsersCount; + myChart3.data.datasets[0].data[1] = data.offlineUsersCount; + myChart3.data.datasets[0].data[2] = data.banUsersCount; + //myChart.data.datasets[0].data[2] = co; + myChart3.update(); } function getClientsInfo() { @@ -58,12 +66,21 @@ $.get(apiUrl, function (res) { var data = res.Data; - //var serverStatus = $.parseJSON(data); updatedServerStatus(data); } ); } + function getUserStatus() { + var apiUrl = basepath + "GetUserStatus"; + $.get(apiUrl, + function (res) { + var data = res.Data; + updatedUserStatus(data); + } + ); + } + //'use strict' // Graphs var ctx1 = document.getElementById('myChart'); @@ -97,6 +114,7 @@ getLogFileTable(10); getClientsInfo(); getServerStatus(); + getUserStatus(); var myChart2 = new Chart(ctx2, { @@ -124,13 +142,13 @@ { type: 'doughnut', data: { - labels: ['活跃用户', '离线用户'/*, 'Yellow', 'Green', 'Purple', 'Orange'*/], + labels: ['活跃用户', '离线用户', '黑名单用户'], datasets: [ { label: '活跃用户', - data: [12, 19], + data: [0, 0], backgroundColor: [ - "rgb(255, 99, 132)", "rgb(54, 162, 235)", "rgb(255, 205, 86)" + "rgb(75, 192, 192)", "rgb(255, 99, 132)", "rgb(201, 203, 207)" ] } ] @@ -143,27 +161,13 @@ } }); - - //$("#myChart").click( - // function (evt) { - // var url = "连接管理"; - // //alert(url); - // } - //); - - //$("#myChart2").click( - // function (evt) { - // var url = " "; - // //alert(url); - // } - //); - - //$("#myChart3").click( - // function (evt) { - // var url = "用户管理"; - // //alert(url); - // } - //); + //red: 'rgb(255, 99, 132)', + // orange: 'rgb(255, 159, 64)', + // yellow: 'rgb(255, 205, 86)', + // green: 'rgb(75, 192, 192)', + // blue: 'rgb(54, 162, 235)', + // purple: 'rgb(153, 102, 255)', + // grey: 'rgb(201, 203, 207)' //定时更新数据 if (window.intevalId) { window.clearInterval(window.intevalId); @@ -173,16 +177,7 @@ getClientsInfo(); getServerStatus(); getLogFileTable(10); - //myChart2.data.datasets.pop(); - //更新数据 - //myChart2.data.datasets[0].data[1] += 3; - //myChart2.data.datasets[1] = 10; - //myChart2.data.datasets.push({ - //label: label, - // backgroundColor: color, - // data: [12, 19] - //}); - //myChart2.update(); + getUserStatus(); }, 5000 ); }()); diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/favicon.ico b/src/NSmartProxy/Web/favicon.ico similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/favicon.ico rename to src/NSmartProxy/Web/favicon.ico diff --git a/src/NSmartProxy/Web/feather.min.js b/src/NSmartProxy/Web/feather.min.js new file mode 100644 index 0000000..5e66d20 --- /dev/null +++ b/src/NSmartProxy/Web/feather.min.js @@ -0,0 +1,16 @@ +!function (e, n) { "object" == typeof exports && "object" == typeof module ? module.exports = n() : "function" == typeof define && define.amd ? define([], n) : "object" == typeof exports ? exports.feather = n() : e.feather = n() }("undefined" != typeof self ? self : this, function () { + return function (e) { var n = {}; function i(t) { if (n[t]) return n[t].exports; var l = n[t] = { i: t, l: !1, exports: {} }; return e[t].call(l.exports, l, l.exports, i), l.l = !0, l.exports } return i.m = e, i.c = n, i.d = function (e, n, t) { i.o(e, n) || Object.defineProperty(e, n, { configurable: !1, enumerable: !0, get: t }) }, i.r = function (e) { Object.defineProperty(e, "__esModule", { value: !0 }) }, i.n = function (e) { var n = e && e.__esModule ? function () { return e.default } : function () { return e }; return i.d(n, "a", n), n }, i.o = function (e, n) { return Object.prototype.hasOwnProperty.call(e, n) }, i.p = "", i(i.s = 80) }([function (e, n, i) { (function (n) { var i = "object", t = function (e) { return e && e.Math == Math && e }; e.exports = t(typeof globalThis == i && globalThis) || t(typeof window == i && window) || t(typeof self == i && self) || t(typeof n == i && n) || Function("return this")() }).call(this, i(75)) }, function (e, n) { var i = {}.hasOwnProperty; e.exports = function (e, n) { return i.call(e, n) } }, function (e, n, i) { var t = i(0), l = i(11), r = i(33), o = i(62), a = t.Symbol, c = l("wks"); e.exports = function (e) { return c[e] || (c[e] = o && a[e] || (o ? a : r)("Symbol." + e)) } }, function (e, n, i) { var t = i(6); e.exports = function (e) { if (!t(e)) throw TypeError(String(e) + " is not an object"); return e } }, function (e, n) { e.exports = function (e) { try { return !!e() } catch (e) { return !0 } } }, function (e, n, i) { var t = i(8), l = i(7), r = i(10); e.exports = t ? function (e, n, i) { return l.f(e, n, r(1, i)) } : function (e, n, i) { return e[n] = i, e } }, function (e, n) { e.exports = function (e) { return "object" == typeof e ? null !== e : "function" == typeof e } }, function (e, n, i) { var t = i(8), l = i(35), r = i(3), o = i(18), a = Object.defineProperty; n.f = t ? a : function (e, n, i) { if (r(e), n = o(n, !0), r(i), l) try { return a(e, n, i) } catch (e) { } if ("get" in i || "set" in i) throw TypeError("Accessors not supported"); return "value" in i && (e[n] = i.value), e } }, function (e, n, i) { var t = i(4); e.exports = !t(function () { return 7 != Object.defineProperty({}, "a", { get: function () { return 7 } }).a }) }, function (e, n) { e.exports = {} }, function (e, n) { e.exports = function (e, n) { return { enumerable: !(1 & e), configurable: !(2 & e), writable: !(4 & e), value: n } } }, function (e, n, i) { var t = i(0), l = i(19), r = i(17), o = t["__core-js_shared__"] || l("__core-js_shared__", {}); (e.exports = function (e, n) { return o[e] || (o[e] = void 0 !== n ? n : {}) })("versions", []).push({ version: "3.1.3", mode: r ? "pure" : "global", copyright: "© 2019 Denis Pushkarev (zloirock.ru)" }) }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var t = o(i(43)), l = o(i(41)), r = o(i(40)); function o(e) { return e && e.__esModule ? e : { default: e } } n.default = Object.keys(l.default).map(function (e) { return new t.default(e, l.default[e], r.default[e]) }).reduce(function (e, n) { return e[n.name] = n, e }, {}) }, function (e, n) { e.exports = ["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"] }, function (e, n, i) { var t = i(72), l = i(20); e.exports = function (e) { return t(l(e)) } }, function (e, n) { e.exports = {} }, function (e, n, i) { var t = i(11), l = i(33), r = t("keys"); e.exports = function (e) { return r[e] || (r[e] = l(e)) } }, function (e, n) { e.exports = !1 }, function (e, n, i) { var t = i(6); e.exports = function (e, n) { if (!t(e)) return e; var i, l; if (n && "function" == typeof (i = e.toString) && !t(l = i.call(e))) return l; if ("function" == typeof (i = e.valueOf) && !t(l = i.call(e))) return l; if (!n && "function" == typeof (i = e.toString) && !t(l = i.call(e))) return l; throw TypeError("Can't convert object to primitive value") } }, function (e, n, i) { var t = i(0), l = i(5); e.exports = function (e, n) { try { l(t, e, n) } catch (i) { t[e] = n } return n } }, function (e, n) { e.exports = function (e) { if (void 0 == e) throw TypeError("Can't call method on " + e); return e } }, function (e, n) { var i = Math.ceil, t = Math.floor; e.exports = function (e) { return isNaN(e = +e) ? 0 : (e > 0 ? t : i)(e) } }, function (e, n, i) { + var t; + /*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames + */ + /*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames + */ + !function () { "use strict"; var i = function () { function e() { } function n(e, n) { for (var i = n.length, t = 0; t < i; ++t)l(e, n[t]) } e.prototype = Object.create(null); var i = {}.hasOwnProperty; var t = /\s+/; function l(e, l) { if (l) { var r = typeof l; "string" === r ? function (e, n) { for (var i = n.split(t), l = i.length, r = 0; r < l; ++r)e[i[r]] = !0 }(e, l) : Array.isArray(l) ? n(e, l) : "object" === r ? function (e, n) { for (var t in n) i.call(n, t) && (e[t] = !!n[t]) }(e, l) : "number" === r && function (e, n) { e[n] = !0 }(e, l) } } return function () { for (var i = arguments.length, t = Array(i), l = 0; l < i; l++)t[l] = arguments[l]; var r = new e; n(r, t); var o = []; for (var a in r) r[a] && o.push(a); return o.join(" ") } }(); void 0 !== e && e.exports ? e.exports = i : void 0 === (t = function () { return i }.apply(n, [])) || (e.exports = t) }() + }, function (e, n, i) { var t = i(7).f, l = i(1), r = i(2)("toStringTag"); e.exports = function (e, n, i) { e && !l(e = i ? e : e.prototype, r) && t(e, r, { configurable: !0, value: n }) } }, function (e, n, i) { var t = i(20); e.exports = function (e) { return Object(t(e)) } }, function (e, n, i) { var t = i(1), l = i(24), r = i(16), o = i(63), a = r("IE_PROTO"), c = Object.prototype; e.exports = o ? Object.getPrototypeOf : function (e) { return e = l(e), t(e, a) ? e[a] : "function" == typeof e.constructor && e instanceof e.constructor ? e.constructor.prototype : e instanceof Object ? c : null } }, function (e, n, i) { "use strict"; var t, l, r, o = i(25), a = i(5), c = i(1), p = i(2), y = i(17), h = p("iterator"), x = !1;[].keys && ("next" in (r = [].keys()) ? (l = o(o(r))) !== Object.prototype && (t = l) : x = !0), void 0 == t && (t = {}), y || c(t, h) || a(t, h, function () { return this }), e.exports = { IteratorPrototype: t, BUGGY_SAFARI_ITERATORS: x } }, function (e, n, i) { var t = i(21), l = Math.min; e.exports = function (e) { return e > 0 ? l(t(e), 9007199254740991) : 0 } }, function (e, n, i) { var t = i(1), l = i(14), r = i(68), o = i(15), a = r(!1); e.exports = function (e, n) { var i, r = l(e), c = 0, p = []; for (i in r) !t(o, i) && t(r, i) && p.push(i); for (; n.length > c;)t(r, i = n[c++]) && (~a(p, i) || p.push(i)); return p } }, function (e, n, i) { var t = i(0), l = i(11), r = i(5), o = i(1), a = i(19), c = i(36), p = i(37), y = p.get, h = p.enforce, x = String(c).split("toString"); l("inspectSource", function (e) { return c.call(e) }), (e.exports = function (e, n, i, l) { var c = !!l && !!l.unsafe, p = !!l && !!l.enumerable, y = !!l && !!l.noTargetGet; "function" == typeof i && ("string" != typeof n || o(i, "name") || r(i, "name", n), h(i).source = x.join("string" == typeof n ? n : "")), e !== t ? (c ? !y && e[n] && (p = !0) : delete e[n], p ? e[n] = i : r(e, n, i)) : p ? e[n] = i : a(n, i) })(Function.prototype, "toString", function () { return "function" == typeof this && y(this).source || c.call(this) }) }, function (e, n) { var i = {}.toString; e.exports = function (e) { return i.call(e).slice(8, -1) } }, function (e, n, i) { var t = i(8), l = i(73), r = i(10), o = i(14), a = i(18), c = i(1), p = i(35), y = Object.getOwnPropertyDescriptor; n.f = t ? y : function (e, n) { if (e = o(e), n = a(n, !0), p) try { return y(e, n) } catch (e) { } if (c(e, n)) return r(!l.f.call(e, n), e[n]) } }, function (e, n, i) { var t = i(0), l = i(31).f, r = i(5), o = i(29), a = i(19), c = i(71), p = i(65); e.exports = function (e, n) { var i, y, h, x, s, u = e.target, d = e.global, f = e.stat; if (i = d ? t : f ? t[u] || a(u, {}) : (t[u] || {}).prototype) for (y in n) { if (x = n[y], h = e.noTargetGet ? (s = l(i, y)) && s.value : i[y], !p(d ? y : u + (f ? "." : "#") + y, e.forced) && void 0 !== h) { if (typeof x == typeof h) continue; c(x, h) } (e.sham || h && h.sham) && r(x, "sham", !0), o(i, y, x, e) } } }, function (e, n) { var i = 0, t = Math.random(); e.exports = function (e) { return "Symbol(".concat(void 0 === e ? "" : e, ")_", (++i + t).toString(36)) } }, function (e, n, i) { var t = i(0), l = i(6), r = t.document, o = l(r) && l(r.createElement); e.exports = function (e) { return o ? r.createElement(e) : {} } }, function (e, n, i) { var t = i(8), l = i(4), r = i(34); e.exports = !t && !l(function () { return 7 != Object.defineProperty(r("div"), "a", { get: function () { return 7 } }).a }) }, function (e, n, i) { var t = i(11); e.exports = t("native-function-to-string", Function.toString) }, function (e, n, i) { var t, l, r, o = i(76), a = i(0), c = i(6), p = i(5), y = i(1), h = i(16), x = i(15), s = a.WeakMap; if (o) { var u = new s, d = u.get, f = u.has, v = u.set; t = function (e, n) { return v.call(u, e, n), n }, l = function (e) { return d.call(u, e) || {} }, r = function (e) { return f.call(u, e) } } else { var g = h("state"); x[g] = !0, t = function (e, n) { return p(e, g, n), n }, l = function (e) { return y(e, g) ? e[g] : {} }, r = function (e) { return y(e, g) } } e.exports = { set: t, get: l, has: r, enforce: function (e) { return r(e) ? l(e) : t(e, {}) }, getterFor: function (e) { return function (n) { var i; if (!c(n) || (i = l(n)).type !== e) throw TypeError("Incompatible receiver, " + e + " required"); return i } } } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var t = Object.assign || function (e) { for (var n = 1; n < arguments.length; n++) { var i = arguments[n]; for (var t in i) Object.prototype.hasOwnProperty.call(i, t) && (e[t] = i[t]) } return e }, l = o(i(22)), r = o(i(12)); function o(e) { return e && e.__esModule ? e : { default: e } } n.default = function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; if ("undefined" == typeof document) throw new Error("`feather.replace()` only works in a browser environment."); var n = document.querySelectorAll("[data-feather]"); Array.from(n).forEach(function (n) { return function (e) { var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, i = function (e) { return Array.from(e.attributes).reduce(function (e, n) { return e[n.name] = n.value, e }, {}) }(e), o = i["data-feather"]; delete i["data-feather"]; var a = r.default[o].toSvg(t({}, n, i, { class: (0, l.default)(n.class, i.class) })), c = (new DOMParser).parseFromString(a, "image/svg+xml").querySelector("svg"); e.parentNode.replaceChild(c, e) }(n, e) }) } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var t, l = i(12), r = (t = l) && t.__esModule ? t : { default: t }; n.default = function (e) { var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; if (console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."), !e) throw new Error("The required `key` (icon name) parameter is missing."); if (!r.default[e]) throw new Error("No icon matching '" + e + "'. See the complete list of icons at https://feathericons.com"); return r.default[e].toSvg(n) } }, function (e) { e.exports = { activity: ["pulse", "health", "action", "motion"], airplay: ["stream", "cast", "mirroring"], "alert-circle": ["warning"], "alert-octagon": ["warning"], "alert-triangle": ["warning"], "at-sign": ["mention"], award: ["achievement", "badge"], aperture: ["camera", "photo"], bell: ["alarm", "notification"], "bell-off": ["alarm", "notification", "silent"], bluetooth: ["wireless"], "book-open": ["read"], book: ["read", "dictionary", "booklet", "magazine"], bookmark: ["read", "clip", "marker", "tag"], briefcase: ["work", "bag", "baggage", "folder"], clipboard: ["copy"], clock: ["time", "watch", "alarm"], "cloud-drizzle": ["weather", "shower"], "cloud-lightning": ["weather", "bolt"], "cloud-rain": ["weather"], "cloud-snow": ["weather", "blizzard"], cloud: ["weather"], codepen: ["logo"], codesandbox: ["logo"], coffee: ["drink", "cup", "mug", "tea", "cafe", "hot", "beverage"], command: ["keyboard", "cmd"], compass: ["navigation", "safari", "travel"], copy: ["clone", "duplicate"], "corner-down-left": ["arrow"], "corner-down-right": ["arrow"], "corner-left-down": ["arrow"], "corner-left-up": ["arrow"], "corner-right-down": ["arrow"], "corner-right-up": ["arrow"], "corner-up-left": ["arrow"], "corner-up-right": ["arrow"], "credit-card": ["purchase", "payment", "cc"], crop: ["photo", "image"], crosshair: ["aim", "target"], database: ["storage"], delete: ["remove"], disc: ["album", "cd", "dvd", "music"], "dollar-sign": ["currency", "money", "payment"], droplet: ["water"], edit: ["pencil", "change"], "edit-2": ["pencil", "change"], "edit-3": ["pencil", "change"], eye: ["view", "watch"], "eye-off": ["view", "watch"], "external-link": ["outbound"], facebook: ["logo"], "fast-forward": ["music"], figma: ["logo", "design", "tool"], film: ["movie", "video"], "folder-minus": ["directory"], "folder-plus": ["directory"], folder: ["directory"], framer: ["logo", "design", "tool"], frown: ["emoji", "face", "bad", "sad", "emotion"], gift: ["present", "box", "birthday", "party"], "git-branch": ["code", "version control"], "git-commit": ["code", "version control"], "git-merge": ["code", "version control"], "git-pull-request": ["code", "version control"], github: ["logo", "version control"], gitlab: ["logo", "version control"], global: ["world", "browser", "language", "translate"], "hard-drive": ["computer", "server"], hash: ["hashtag", "number", "pound"], headphones: ["music", "audio"], heart: ["like", "love"], "help-circle": ["question mark"], hexagon: ["shape", "node.js", "logo"], home: ["house"], image: ["picture"], inbox: ["email"], instagram: ["logo", "camera"], key: ["password", "login", "authentication"], "life-bouy": ["help", "life ring", "support"], linkedin: ["logo"], lock: ["security", "password"], "log-in": ["sign in", "arrow"], "log-out": ["sign out", "arrow"], mail: ["email"], "map-pin": ["location", "navigation", "travel", "marker"], map: ["location", "navigation", "travel"], maximize: ["fullscreen"], "maximize-2": ["fullscreen", "arrows"], meh: ["emoji", "face", "neutral", "emotion"], menu: ["bars", "navigation", "hamburger"], "message-circle": ["comment", "chat"], "message-square": ["comment", "chat"], "mic-off": ["record"], mic: ["record"], minimize: ["exit fullscreen"], "minimize-2": ["exit fullscreen", "arrows"], monitor: ["tv"], moon: ["dark", "night"], "more-horizontal": ["ellipsis"], "more-vertical": ["ellipsis"], "mouse-pointer": ["arrow", "cursor"], move: ["arrows"], navigation: ["location", "travel"], "navigation-2": ["location", "travel"], octagon: ["stop"], package: ["box"], paperclip: ["attachment"], pause: ["music", "stop"], "pause-circle": ["music", "stop"], "pen-tool": ["vector", "drawing"], play: ["music", "start"], "play-circle": ["music", "start"], plus: ["add", "new"], "plus-circle": ["add", "new"], "plus-square": ["add", "new"], pocket: ["logo", "save"], power: ["on", "off"], radio: ["signal"], rewind: ["music"], rss: ["feed", "subscribe"], save: ["floppy disk"], search: ["find", "magnifier", "magnifying glass"], send: ["message", "mail", "paper airplane"], settings: ["cog", "edit", "gear", "preferences"], shield: ["security"], "shield-off": ["security"], "shopping-bag": ["ecommerce", "cart", "purchase", "store"], "shopping-cart": ["ecommerce", "cart", "purchase", "store"], shuffle: ["music"], "skip-back": ["music"], "skip-forward": ["music"], slash: ["ban", "no"], sliders: ["settings", "controls"], smile: ["emoji", "face", "happy", "good", "emotion"], speaker: ["music"], star: ["bookmark", "favorite", "like"], sun: ["brightness", "weather", "light"], sunrise: ["weather"], sunset: ["weather"], tag: ["label"], target: ["bullseye"], terminal: ["code", "command line"], "thumbs-down": ["dislike", "bad"], "thumbs-up": ["like", "good"], "toggle-left": ["on", "off", "switch"], "toggle-right": ["on", "off", "switch"], trash: ["garbage", "delete", "remove"], "trash-2": ["garbage", "delete", "remove"], triangle: ["delta"], truck: ["delivery", "van", "shipping"], twitter: ["logo"], umbrella: ["rain", "weather"], "video-off": ["camera", "movie", "film"], video: ["camera", "movie", "film"], voicemail: ["phone"], volume: ["music", "sound", "mute"], "volume-1": ["music", "sound"], "volume-2": ["music", "sound"], "volume-x": ["music", "sound", "mute"], watch: ["clock", "time"], wind: ["weather", "air"], "x-circle": ["cancel", "close", "delete", "remove", "times"], "x-octagon": ["delete", "stop", "alert", "warning", "times"], "x-square": ["cancel", "close", "delete", "remove", "times"], x: ["cancel", "close", "delete", "remove", "times"], youtube: ["logo", "video", "play"], "zap-off": ["flash", "camera", "lightning"], zap: ["flash", "camera", "lightning"] } }, function (e) { e.exports = { activity: '', airplay: '', "alert-circle": '', "alert-octagon": '', "alert-triangle": '', "align-center": '', "align-justify": '', "align-left": '', "align-right": '', anchor: '', aperture: '', archive: '', "arrow-down-circle": '', "arrow-down-left": '', "arrow-down-right": '', "arrow-down": '', "arrow-left-circle": '', "arrow-left": '', "arrow-right-circle": '', "arrow-right": '', "arrow-up-circle": '', "arrow-up-left": '', "arrow-up-right": '', "arrow-up": '', "at-sign": '', award: '', "bar-chart-2": '', "bar-chart": '', "battery-charging": '', battery: '', "bell-off": '', bell: '', bluetooth: '', bold: '', "book-open": '', book: '', bookmark: '', box: '', briefcase: '', calendar: '', "camera-off": '', camera: '', cast: '', "check-circle": '', "check-square": '', check: '', "chevron-down": '', "chevron-left": '', "chevron-right": '', "chevron-up": '', "chevrons-down": '', "chevrons-left": '', "chevrons-right": '', "chevrons-up": '', chrome: '', circle: '', clipboard: '', clock: '', "cloud-drizzle": '', "cloud-lightning": '', "cloud-off": '', "cloud-rain": '', "cloud-snow": '', cloud: '', code: '', codepen: '', codesandbox: '', coffee: '', columns: '', command: '', compass: '', copy: '', "corner-down-left": '', "corner-down-right": '', "corner-left-down": '', "corner-left-up": '', "corner-right-down": '', "corner-right-up": '', "corner-up-left": '', "corner-up-right": '', cpu: '', "credit-card": '', crop: '', crosshair: '', database: '', delete: '', disc: '', "dollar-sign": '', "download-cloud": '', download: '', droplet: '', "edit-2": '', "edit-3": '', edit: '', "external-link": '', "eye-off": '', eye: '', facebook: '', "fast-forward": '', feather: '', figma: '', "file-minus": '', "file-plus": '', "file-text": '', file: '', film: '', filter: '', flag: '', "folder-minus": '', "folder-plus": '', folder: '', framer: '', frown: '', gift: '', "git-branch": '', "git-commit": '', "git-merge": '', "git-pull-request": '', github: '', gitlab: '', globe: '', grid: '', "hard-drive": '', hash: '', headphones: '', heart: '', "help-circle": '', hexagon: '', home: '', image: '', inbox: '', info: '', instagram: '', italic: '', key: '', layers: '', layout: '', "life-buoy": '', "link-2": '', link: '', linkedin: '', list: '', loader: '', lock: '', "log-in": '', "log-out": '', mail: '', "map-pin": '', map: '', "maximize-2": '', maximize: '', meh: '', menu: '', "message-circle": '', "message-square": '', "mic-off": '', mic: '', "minimize-2": '', minimize: '', "minus-circle": '', "minus-square": '', minus: '', monitor: '', moon: '', "more-horizontal": '', "more-vertical": '', "mouse-pointer": '', move: '', music: '', "navigation-2": '', navigation: '', octagon: '', package: '', paperclip: '', "pause-circle": '', pause: '', "pen-tool": '', percent: '', "phone-call": '', "phone-forwarded": '', "phone-incoming": '', "phone-missed": '', "phone-off": '', "phone-outgoing": '', phone: '', "pie-chart": '', "play-circle": '', play: '', "plus-circle": '', "plus-square": '', plus: '', pocket: '', power: '', printer: '', radio: '', "refresh-ccw": '', "refresh-cw": '', repeat: '', rewind: '', "rotate-ccw": '', "rotate-cw": '', rss: '', save: '', scissors: '', search: '', send: '', server: '', settings: '', "share-2": '', share: '', "shield-off": '', shield: '', "shopping-bag": '', "shopping-cart": '', shuffle: '', sidebar: '', "skip-back": '', "skip-forward": '', slack: '', slash: '', sliders: '', smartphone: '', smile: '', speaker: '', square: '', star: '', "stop-circle": '', sun: '', sunrise: '', sunset: '', tablet: '', tag: '', target: '', terminal: '', thermometer: '', "thumbs-down": '', "thumbs-up": '', "toggle-left": '', "toggle-right": '', tool: '', "trash-2": '', trash: '', trello: '', "trending-down": '', "trending-up": '', triangle: '', truck: '', tv: '', twitch: '', twitter: '', type: '', umbrella: '', underline: '', unlock: '', "upload-cloud": '', upload: '', "user-check": '', "user-minus": '', "user-plus": '', "user-x": '', user: '', users: '', "video-off": '', video: '', voicemail: '', "volume-1": '', "volume-2": '', "volume-x": '', volume: '', watch: '', "wifi-off": '', wifi: '', wind: '', "x-circle": '', "x-octagon": '', "x-square": '', x: '', youtube: '', "zap-off": '', zap: '', "zoom-in": '', "zoom-out": '' } }, function (e) { e.exports = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var t = Object.assign || function (e) { for (var n = 1; n < arguments.length; n++) { var i = arguments[n]; for (var t in i) Object.prototype.hasOwnProperty.call(i, t) && (e[t] = i[t]) } return e }, l = function () { function e(e, n) { for (var i = 0; i < n.length; i++) { var t = n[i]; t.enumerable = t.enumerable || !1, t.configurable = !0, "value" in t && (t.writable = !0), Object.defineProperty(e, t.key, t) } } return function (n, i, t) { return i && e(n.prototype, i), t && e(n, t), n } }(), r = a(i(22)), o = a(i(42)); function a(e) { return e && e.__esModule ? e : { default: e } } var c = function () { function e(n, i) { var l = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : []; !function (e, n) { if (!(e instanceof n)) throw new TypeError("Cannot call a class as a function") }(this, e), this.name = n, this.contents = i, this.tags = l, this.attrs = t({}, o.default, { class: "feather feather-" + n }) } return l(e, [{ key: "toSvg", value: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; return "" + this.contents + "" } }, { key: "toString", value: function () { return this.contents } }]), e }(); n.default = c }, function (e, n, i) { "use strict"; var t = o(i(12)), l = o(i(39)), r = o(i(38)); function o(e) { return e && e.__esModule ? e : { default: e } } e.exports = { icons: t.default, toSvg: l.default, replace: r.default } }, function (e, n, i) { e.exports = i(0) }, function (e, n, i) { var t = i(2)("iterator"), l = !1; try { var r = 0, o = { next: function () { return { done: !!r++ } }, return: function () { l = !0 } }; o[t] = function () { return this }, Array.from(o, function () { throw 2 }) } catch (e) { } e.exports = function (e, n) { if (!n && !l) return !1; var i = !1; try { var r = {}; r[t] = function () { return { next: function () { return { done: i = !0 } } } }, e(r) } catch (e) { } return i } }, function (e, n, i) { var t = i(30), l = i(2)("toStringTag"), r = "Arguments" == t(function () { return arguments }()); e.exports = function (e) { var n, i, o; return void 0 === e ? "Undefined" : null === e ? "Null" : "string" == typeof (i = function (e, n) { try { return e[n] } catch (e) { } }(n = Object(e), l)) ? i : r ? t(n) : "Object" == (o = t(n)) && "function" == typeof n.callee ? "Arguments" : o } }, function (e, n, i) { var t = i(47), l = i(9), r = i(2)("iterator"); e.exports = function (e) { if (void 0 != e) return e[r] || e["@@iterator"] || l[t(e)] } }, function (e, n, i) { "use strict"; var t = i(18), l = i(7), r = i(10); e.exports = function (e, n, i) { var o = t(n); o in e ? l.f(e, o, r(0, i)) : e[o] = i } }, function (e, n, i) { var t = i(2), l = i(9), r = t("iterator"), o = Array.prototype; e.exports = function (e) { return void 0 !== e && (l.Array === e || o[r] === e) } }, function (e, n, i) { var t = i(3); e.exports = function (e, n, i, l) { try { return l ? n(t(i)[0], i[1]) : n(i) } catch (n) { var r = e.return; throw void 0 !== r && t(r.call(e)), n } } }, function (e, n) { e.exports = function (e) { if ("function" != typeof e) throw TypeError(String(e) + " is not a function"); return e } }, function (e, n, i) { var t = i(52); e.exports = function (e, n, i) { if (t(e), void 0 === n) return e; switch (i) { case 0: return function () { return e.call(n) }; case 1: return function (i) { return e.call(n, i) }; case 2: return function (i, t) { return e.call(n, i, t) }; case 3: return function (i, t, l) { return e.call(n, i, t, l) } }return function () { return e.apply(n, arguments) } } }, function (e, n, i) { "use strict"; var t = i(53), l = i(24), r = i(51), o = i(50), a = i(27), c = i(49), p = i(48); e.exports = function (e) { var n, i, y, h, x = l(e), s = "function" == typeof this ? this : Array, u = arguments.length, d = u > 1 ? arguments[1] : void 0, f = void 0 !== d, v = 0, g = p(x); if (f && (d = t(d, u > 2 ? arguments[2] : void 0, 2)), void 0 == g || s == Array && o(g)) for (i = new s(n = a(x.length)); n > v; v++)c(i, v, f ? d(x[v], v) : x[v]); else for (h = g.call(x), i = new s; !(y = h.next()).done; v++)c(i, v, f ? r(h, d, [y.value, v], !0) : y.value); return i.length = v, i } }, function (e, n, i) { var t = i(32), l = i(54); t({ target: "Array", stat: !0, forced: !i(46)(function (e) { Array.from(e) }) }, { from: l }) }, function (e, n, i) { var t = i(6), l = i(3); e.exports = function (e, n) { if (l(e), !t(n) && null !== n) throw TypeError("Can't set " + String(n) + " as a prototype") } }, function (e, n, i) { var t = i(56); e.exports = Object.setPrototypeOf || ("__proto__" in {} ? function () { var e, n = !1, i = {}; try { (e = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set).call(i, []), n = i instanceof Array } catch (e) { } return function (i, l) { return t(i, l), n ? e.call(i, l) : i.__proto__ = l, i } }() : void 0) }, function (e, n, i) { var t = i(0).document; e.exports = t && t.documentElement }, function (e, n, i) { var t = i(28), l = i(13); e.exports = Object.keys || function (e) { return t(e, l) } }, function (e, n, i) { var t = i(8), l = i(7), r = i(3), o = i(59); e.exports = t ? Object.defineProperties : function (e, n) { r(e); for (var i, t = o(n), a = t.length, c = 0; a > c;)l.f(e, i = t[c++], n[i]); return e } }, function (e, n, i) { var t = i(3), l = i(60), r = i(13), o = i(15), a = i(58), c = i(34), p = i(16)("IE_PROTO"), y = function () { }, h = function () { var e, n = c("iframe"), i = r.length; for (n.style.display = "none", a.appendChild(n), n.src = String("javascript:"), (e = n.contentWindow.document).open(), e.write(" diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/logs.html b/src/NSmartProxy/Web/logs.html similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/logs.html rename to src/NSmartProxy/Web/logs.html diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/logs.js b/src/NSmartProxy/Web/logs.js similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/logs.js rename to src/NSmartProxy/Web/logs.js diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/main.html b/src/NSmartProxy/Web/main.html similarity index 98% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/main.html rename to src/NSmartProxy/Web/main.html index b63080d..f3ba950 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/main.html +++ b/src/NSmartProxy/Web/main.html @@ -133,11 +133,11 @@ - + - + diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/main.js b/src/NSmartProxy/Web/main.js similarity index 82% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/main.js rename to src/NSmartProxy/Web/main.js index 020b7e7..467acf6 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/main.js +++ b/src/NSmartProxy/Web/main.js @@ -1,17 +1,17 @@ //TODO 调试用 -//var basepath = "/";//api根地址,这里需要和配置文件一致 -var basepath = "http://localhost:12309/"; +var basepath = "/";//api根地址,这里需要和配置文件一致 +//var basepath = "http://localhost:12309/"; //调试时请把地址改成这个 //hashchange事件,路由是如此实现的 (function () { if ("onhashchange" in window) { // event supported? window.onhashchange = function () { hashChanged(window.location.hash); - } + }; } else { // event not supported: var storedHash = window.location.hash; window.setInterval(function () { - if (window.location.hash != storedHash) { + if (window.location.hash !== storedHash) { storedHash = window.location.hash; hashChanged(storedHash); } @@ -21,7 +21,7 @@ var basepath = "http://localhost:12309/"; function hashChanged(storedHash) { storedHash = loadContent(storedHash); - if (location.pathname.toUpperCase() !="/LOGIN.HTML") { + if (location.pathname.toUpperCase() !== "/LOGIN.HTML") { if (getCookie("NSPTK").length < 1) { location.href = "/login.html"; } @@ -29,7 +29,7 @@ var basepath = "http://localhost:12309/"; } function loadContent(storedHash) { - if (this.location.href.indexOf('#') < 0) { + if (this.location.href.indexOf("login.html") < 0 && this.location.href.indexOf('#') < 0) { this.location.href += "#dashboard"; return; } @@ -77,7 +77,7 @@ function getCookie(cookieName) { var arrCookie = strCookie.split("; "); for (var i = 0; i < arrCookie.length; i++) { var arr = arrCookie[i].split("="); - if (cookieName == arr[0]) { + if (cookieName === arr[0]) { return arr[1]; } } @@ -88,7 +88,7 @@ function delCookie(name) { var exp = new Date(); exp.setTime(exp.getTime() - 1); var cval = getCookie(name); - if (cval != null) document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString(); + if (cval !== null) document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString(); } function signOut() { diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/signin.css b/src/NSmartProxy/Web/signin.css similarity index 100% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/signin.css rename to src/NSmartProxy/Web/signin.css diff --git a/src/NSmartProxy/Extension/HttpServerStaticFiles/users.html b/src/NSmartProxy/Web/users.html similarity index 65% rename from src/NSmartProxy/Extension/HttpServerStaticFiles/users.html rename to src/NSmartProxy/Web/users.html index 01f75f1..4891d08 100644 --- a/src/NSmartProxy/Extension/HttpServerStaticFiles/users.html +++ b/src/NSmartProxy/Web/users.html @@ -1,4 +1,12 @@ -
+ +

用户

@@ -16,6 +24,22 @@

用户

+
+
+
+ + + +
PortPort(Protocol,Host) User ID AppIdDescription Status Connections 无效内容 无效内容 无效内容无效内容
@@ -41,7 +43,8 @@

连接

无效内容 无效内容 无效内容 无效内容
" + co.port + "" + co.port + "(" + co.protocol + "," + co.host + ")" + co.clientId + "" + co.appId + "" + co.description + "" + statusHtml + "连接数:" + co.revconns.length + ",隧道数:" + co.tunnels.length + "