This commit is contained in:
snltty
2023-09-14 15:16:12 +08:00
commit eb08f03738
173 changed files with 14799 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.vs
bin
obj
node_modules
/public/*

63
README.md Normal file
View File

@@ -0,0 +1,63 @@
<!--
* @Author: snltty
* @Date: 2021-08-22 14:09:03
* @LastEditors: snltty
* @LastEditTime: 2022-11-21 16:36:26
* @version: v1.0.0
* @Descripttion: 功能说明
* @FilePath: \client.service.ui.webd:\desktop\cminitor\README.md
-->
<div align="center">
<p><img src="./readme/logo.png" height="150"></p>
# class monitor
#### Visual Studio 2022 LTSC 17.4.1
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">QQ 群1121552990</a>
![GitHub Repo stars](https://img.shields.io/github/stars/snltty/cminitor?style=social)
![GitHub Repo forks](https://img.shields.io/github/forks/snltty/cminitor?style=social)
[![star](https://gitee.com/snltty/cminitor/badge/star.svg?theme=dark)](https://gitee.com/snltty/cminitor/stargazers)
[![fork](https://gitee.com/snltty/cminitor/badge/fork.svg?theme=dark)](https://gitee.com/snltty/cminitor/members)
使用前请确保你已知其中风险
本软件仅供学习交流,请勿用于违法犯罪
</div>
## 说明
1. 这是一个粗略的局域网监控程序(说是局域网,你放外网也不是不行)
2. 桌面捕获很粗略,只是做了一个减小图片尺寸,没有做区域更新
## 看图
<p><img src="./readme/cmonitor.jpg" height="150"></p>
## 支持平台
- [x] 客户端支持 **【windows】**
- [x] 服务端支持 **【windows】**、**【linux】**
## 运行参数
### 公共参数
- [x] **【--mode】** 运行模式 **client,server**
### 1、客户端
- [x] **【--server】** 服务器ip **192.168.1.18**
- **【--name】** 机器名 **Dns.GetHostName()**
- **【--username-key】** 用户名内存共享key谁在用此设备 **cmonitor/username**
- **【--username-len】** 用户名内存共享长度 **255**
- **【--keyboard-key】** 键盘按键内存共享key按下哪些按键 **cmonitor/keyboard**
- **【--keyboard-len】** 键盘按键内存共享长度 **255**
- **【--share-key】** 自定义其它数据共享 **cmonitor/share**
- **【--share-len】** 长度 **255**
### 2、服务端
- [x] **【--web】** 管理UI端口 **1800**
- [x] **【--api】** 管理接口端口 **1801**
- [x] **【--service】** 服务端口 **1802**
## 支持作者
请作者喝一杯咖啡,使其更有精力更新代码
<p><img src="./readme/qr.jpg" width="360"></p>

68
cmonitor.sln Normal file
View File

@@ -0,0 +1,68 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33110.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmonitor", "cmonitor\cmonitor.csproj", "{267DE8BE-F91C-4CCB-9D58-D33FDA661126}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "common.libs", "common.libs\common.libs.csproj", "{00EECF97-99EB-4B12-AAEF-ED2363914275}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "message.win", "message.win\message.win.csproj", "{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "wallpaper.win", "wallpaper.win\wallpaper.win.csproj", "{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "llock.win", "llock.win\llock.win.csproj", "{DD7F9ABD-7718-454B-950D-A369C6D85FE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmonitor.win", "cmonitor.win\cmonitor.win.csproj", "{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
ReleaseLinux|Any CPU = ReleaseLinux|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Debug|Any CPU.Build.0 = Debug|Any CPU
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Release|Any CPU.ActiveCfg = Release|Any CPU
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.Release|Any CPU.Build.0 = Release|Any CPU
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{267DE8BE-F91C-4CCB-9D58-D33FDA661126}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.Release|Any CPU.Build.0 = Release|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{00EECF97-99EB-4B12-AAEF-ED2363914275}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.Release|Any CPU.Build.0 = Release|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{386F8B8F-2E83-408E-AC3A-4BD35608EDE3}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.Release|Any CPU.Build.0 = Release|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{88FF2017-FF1A-4E9F-AB2E-2973C5B35C34}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.Release|Any CPU.Build.0 = Release|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{DD7F9ABD-7718-454B-950D-A369C6D85FE4}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.Release|Any CPU.Build.0 = Release|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.ReleaseLinux|Any CPU.ActiveCfg = ReleaseLinux|Any CPU
{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}.ReleaseLinux|Any CPU.Build.0 = ReleaseLinux|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F7E3D69-2821-4CA8-A8B5-86016FA9BEAB}
EndGlobalSection
EndGlobal

1
cmonitor.web Submodule

Submodule cmonitor.web added at b5a3da842b

6
cmonitor.win/App.config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

50
cmonitor.win/Form1.Designer.cs generated Normal file
View File

@@ -0,0 +1,50 @@
namespace cmonitor.win
{
partial class Form1
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows
/// <summary>
/// 设计器支持所需的方法 - 不要修改
/// 使用代码编辑器修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(0, 0);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "Form1";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnClose);
this.Load += new System.EventHandler(this.OnLoad);
this.ResumeLayout(false);
}
#endregion
}
}

104
cmonitor.win/Form1.cs Normal file
View File

@@ -0,0 +1,104 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
namespace cmonitor.win
{
public partial class Form1 : Form
{
private Process proc;
private string[] args;
protected override CreateParams CreateParams
{
get
{
const int WS_EX_APPWINDOW = 0x40000;
const int WS_EX_TOOLWINDOW = 0x80;
CreateParams cp = base.CreateParams;
cp.ExStyle &= (~WS_EX_APPWINDOW);
cp.ExStyle |= WS_EX_TOOLWINDOW;
return cp;
}
}
public Form1(string[] args)
{
this.args = args;
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
this.WindowState = FormWindowState.Minimized;
this.Hide();
this.Opacity = 0;
AppDomain.CurrentDomain.ProcessExit += (s, e) => KillExe();
Application.ApplicationExit += (s, e) => KillExe();
}
private bool OpenExe()
{
try
{
string filename = Process.GetCurrentProcess().MainModule.FileName;
string dir = Path.GetDirectoryName(filename);
string file = Path.Combine(dir, "./cmonitor.exe");
ProcessStartInfo processStartInfo = new ProcessStartInfo()
{
WorkingDirectory = dir,
FileName = file,
CreateNoWindow = false,
ErrorDialog = false,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Join(" ", this.args),
Verb = "runas",
};
proc = Process.Start(processStartInfo);
return true;
}
catch (Exception)
{
try
{
proc.Kill();
proc.Dispose();
}
catch (Exception)
{
}
proc = null;
}
return false;
}
private void KillExe()
{
try
{
proc?.Close();
proc?.Dispose();
}
catch (Exception)
{
}
finally
{
proc = null;
}
}
private void OnLoad(object sender, EventArgs e)
{
OpenExe();
}
private void OnClose(object sender, FormClosingEventArgs e)
{
KillExe();
}
}
}

120
cmonitor.win/Form1.resx Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

29
cmonitor.win/Program.cs Normal file
View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace cmonitor.win
{
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out bool isAppRunning);
if (isAppRunning == false)
{
Environment.Exit(1);
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args));
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("cmonitor.win")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("cmonitor.win")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("9170e23a-b7ca-485f-ae8a-6bc9d29d4c67")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本: 4.0.30319.42000
//
// 对此文件的更改可能导致不正确的行为,如果
// 重新生成代码,则所做更改将丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace cmonitor.win.Properties
{
/// <summary>
/// 强类型资源类,用于查找本地化字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// 返回此类使用的缓存 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("cmonitor.win.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace cmonitor.win.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

79
cmonitor.win/app.manifest Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9170E23A-B7CA-485F-AE8A-6BC9D29D4C67}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>cmonitor.win</RootNamespace>
<AssemblyName>cmonitor.win</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>embedded</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\cmonitor\web\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseLinux|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ReleaseLinux\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>embedded</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

385
cmonitor/Program.cs Normal file
View File

@@ -0,0 +1,385 @@
using cmonitor.hijack;
using cmonitor.server.api;
using cmonitor.server.api.services;
using cmonitor.server.client;
using cmonitor.server.client.reports.active;
using cmonitor.server.client.reports.light;
using cmonitor.server.client.reports.hijack;
using cmonitor.server.client.reports.llock;
using cmonitor.server.client.reports.screen;
using cmonitor.server.client.reports.volume;
using cmonitor.server.service;
using cmonitor.server.service.messengers.active;
using cmonitor.server.service.messengers.hijack;
using cmonitor.server.service.messengers.llock;
using cmonitor.server.service.messengers.report;
using cmonitor.server.service.messengers.screen;
using cmonitor.server.service.messengers.sign;
using cmonitor.server.service.messengers.usb;
using cmonitor.server.service.messengers.volume;
using cmonitor.server.service.messengers.wallpaper;
using cmonitor.server.web;
using common.libs;
using common.libs.database;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using cmonitor.server.client.reports;
using common.libs.extends;
using cmonitor.server.service.messengers.light;
using System.Reflection;
namespace cmonitor
{
internal class Program
{
static async Task Main(string[] args)
{
Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out bool isAppRunning);
if (isAppRunning == false)
{
Environment.Exit(1);
}
AppDomain.CurrentDomain.UnhandledException += (a, b) =>
{
Logger.Instance.Error(b.ExceptionObject + "");
};
ThreadPool.SetMinThreads(1024, 1024);
ThreadPool.SetMaxThreads(65535, 65535);
LoggerConsole();
Config config = new Config();
Dictionary<string, string> dic = ArgumentParser.Parse(args, out string error);
config.BroadcastIP = IPAddress.Parse(dic["server"]);
config.Name = dic["name"];
config.WebPort = int.Parse(dic["web"]);
config.ApiPort = int.Parse(dic["api"]);
config.ServicePort = int.Parse(dic["service"]);
config.UserNameMemoryKey = dic["username-key"];
config.UserNameMemoryLength = int.Parse(dic["username-len"]);
config.KeyboardMemoryKey = dic["keyboard-key"];
config.KeyboardMemoryLength = int.Parse(dic["keyboard-len"]);
config.ShareMemoryKey = dic["share-key"];
config.ShareMemoryLength = int.Parse(dic["share-len"]);
Logger.Instance.Debug($"config:{config.ToJson()}");
Logger.Instance.Debug($"args:{string.Join(" ", args)}");
config.IsCLient = dic.ContainsKey("mode") && dic["mode"].Contains("client");
config.IsServer = dic.ContainsKey("mode") && dic["mode"].Contains("server");
//注入对象
ServiceProvider serviceProvider = null;
ServiceCollection serviceCollection = new ServiceCollection();
//注入 依赖注入服务供应 使得可以在别的地方通过注入的方式获得 ServiceProvider 以用来获取其它服务
serviceCollection.AddSingleton((e) => serviceProvider);
serviceCollection.AddSingleton<Config>((a) => config);
serviceCollection.AddTransient(typeof(IConfigDataProvider<>), typeof(ConfigDataFileProvider<>));
//劫持
serviceCollection.AddSingleton<HijackConfig>();
serviceCollection.AddSingleton<HijackController>();
serviceCollection.AddSingleton<HijackEventHandler>();
//客户端
serviceCollection.AddSingleton<ClientSignInState>();
serviceCollection.AddSingleton<ClientTransfer>();
serviceCollection.AddSingleton<ClientConfig>();
serviceCollection.AddSingleton<ReportTransfer>();
serviceCollection.AddSingleton<ActiveWindowReport>();
serviceCollection.AddSingleton<HijackReport>();
serviceCollection.AddSingleton<LLockReport>();
serviceCollection.AddSingleton<ScreenReport>();
serviceCollection.AddSingleton<UsbReport>();
serviceCollection.AddSingleton<VolumeReport>();
serviceCollection.AddSingleton<WallpaperReport>();
serviceCollection.AddSingleton<LightReport>();
serviceCollection.AddSingleton<ShareReport>();
//服务
serviceCollection.AddSingleton<TcpServer>();
serviceCollection.AddSingleton<MessengerSender>();
serviceCollection.AddSingleton<MessengerResolver>();
serviceCollection.AddSingleton<SignCaching>();
serviceCollection.AddSingleton<SignInMessenger>();
serviceCollection.AddSingleton<ReportMessenger>();
serviceCollection.AddSingleton<CommandMessenger>();
serviceCollection.AddSingleton<HijackMessenger>();
serviceCollection.AddSingleton<ActiveMessenger>();
serviceCollection.AddSingleton<LLockMessenger>();
serviceCollection.AddSingleton<ScreenMessenger>();
serviceCollection.AddSingleton<UsbMessenger>();
serviceCollection.AddSingleton<VolumeMessenger>();
serviceCollection.AddSingleton<WallpaperMessenger>();
serviceCollection.AddSingleton<LightMessenger>();
//api
serviceCollection.AddSingleton<RuleConfig>();
serviceCollection.AddSingleton<IClientServer, ClientServer>();
serviceCollection.AddSingleton<SignInClientService>();
serviceCollection.AddSingleton<CommandClientService>();
serviceCollection.AddSingleton<ReportClientService>();
serviceCollection.AddSingleton<HijackClientService>();
serviceCollection.AddSingleton<ActiveClientService>();
serviceCollection.AddSingleton<LLockClientService>();
serviceCollection.AddSingleton<ScreenClientService>();
serviceCollection.AddSingleton<UsbClientService>();
serviceCollection.AddSingleton<VolumeClientService>();
serviceCollection.AddSingleton<WallpaperClientService>();
serviceCollection.AddSingleton<LightClientService>();
//web
serviceCollection.AddSingleton<IWebServer, WebServer>();
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
serviceProvider = serviceCollection.BuildServiceProvider();
if (config.IsCLient || config.IsServer)
{
MessengerResolver messengerResolver = serviceProvider.GetService<MessengerResolver>();
messengerResolver.LoadMessenger(assemblies);
}
if (config.IsServer)
{
Logger.Instance.Info($"start server");
//api
IClientServer clientServer = serviceProvider.GetService<IClientServer>();
clientServer.LoadPlugins(assemblies);
clientServer.Websocket();
Logger.Instance.Info($"api listen:{config.ApiPort}");
//web
IWebServer webServer = serviceProvider.GetService<IWebServer>();
webServer.Start();
Logger.Instance.Info($"web listen:{config.WebPort}");
//服务
TcpServer tcpServer = serviceProvider.GetService<TcpServer>();
tcpServer.Start();
Logger.Instance.Info($"service listen:{config.ServicePort}");
}
if (config.IsCLient)
{
Logger.Instance.Info($"start client");
Logger.Instance.Info($"server ip {config.BroadcastIP}");
ReportTransfer report = serviceProvider.GetService<ReportTransfer>();
report.LoadPlugins(assemblies);
ClientTransfer clientTransfer = serviceProvider.GetService<ClientTransfer>();
}
GCHelper.FlushMemory();
await Helper.Await();
}
private static void LoggerConsole()
{
if (Directory.Exists("log") == false)
{
Directory.CreateDirectory("log");
}
Logger.Instance.OnLogger += (model) =>
{
ConsoleColor currentForeColor = Console.ForegroundColor;
switch (model.Type)
{
case LoggerTypes.DEBUG:
Console.ForegroundColor = ConsoleColor.Blue;
break;
case LoggerTypes.INFO:
Console.ForegroundColor = ConsoleColor.White;
break;
case LoggerTypes.WARNING:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case LoggerTypes.ERROR:
Console.ForegroundColor = ConsoleColor.Red;
break;
default:
break;
}
string line = $"[{model.Type,-7}][{model.Time:yyyy-MM-dd HH:mm:ss}]:{model.Content}";
Console.WriteLine(line);
Console.ForegroundColor = currentForeColor;
try
{
using StreamWriter sw = File.AppendText(Path.Combine("log", $"{DateTime.Now:yyyy-MM-dd}.log"));
sw.WriteLine(line);
sw.Flush();
sw.Close();
sw.Dispose();
}
catch (Exception)
{
}
};
}
}
public sealed class Config
{
public int WebPort { get; set; } = 1800;
public int ApiPort { get; set; } = 1801;
public int ServicePort { get; set; } = 1802;
public IPAddress BroadcastIP { get; set; } = IPAddress.Parse("192.168.1.35");
public bool IsCLient { get; set; }
public bool IsServer { get; set; }
public string WebRoot { get; set; } = "./web/";
public string Name { get; set; } = Dns.GetHostName();
public string Version { get; set; } = "1.0.0.1";
public string UserNameMemoryKey { get; set; } = "cmonitor/username";
public string KeyboardMemoryKey { get; set; } = "cmonitor/keyboard";
public int UserNameMemoryLength { get; set; } = 255;
public int KeyboardMemoryLength { get; set; } = 255;
public string ShareMemoryKey { get; set; } = "cmonitor/sharememory";
public int ShareMemoryLength { get; set; } = 1024;
public const int ReportTime = 30;
public const int ScreenTime = 200;
}
public class ArgumentParser
{
public static Dictionary<string, string> Parse(string[] args, out string error)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
for (int i = 0; i < args.Length; i++)
{
if (args[i].IndexOf("--") == 0)
{
if (i + 1 < args.Length && args[i + 1].IndexOf("--") == -1)
{
dic.Add(args[i].Substring(2), args[i + 1]);
i++;
}
else
{
dic.Add(args[i].Substring(2), string.Empty);
}
}
}
Validate(dic, out error);
return dic;
}
static bool Validate(Dictionary<string, string> dic, out string error)
{
error = string.Empty;
return ValidateMode(dic) &&
ValidateServer(dic, out error) && ValidateName(dic, out error) && ValidatePort(dic, out error) && ValidateMemoryKey(dic, out error);
}
static bool ValidateMode(Dictionary<string, string> dic)
{
//模式
if (dic.ContainsKey("mode") == false || (dic["mode"].Contains("client") == false && dic["mode"].Contains("server") == false))
{
dic["mode"] = "server,client";
}
return true;
}
static bool ValidateServer(Dictionary<string, string> dic, out string error)
{
error = string.Empty;
//服务器地址
if (dic.ContainsKey("server") == false || string.IsNullOrWhiteSpace(dic["server"]))
{
dic["server"] = "192.168.1.35";
}
return true;
}
static bool ValidateName(Dictionary<string, string> dic, out string error)
{
error = string.Empty;
//服务器地址
if (dic.ContainsKey("name") == false || string.IsNullOrWhiteSpace(dic["name"]))
{
dic["name"] = Dns.GetHostName();
if (dic["name"].Length > 12)
{
dic["name"] = dic["name"].Substring(0, 12);
}
}
return true;
}
static bool ValidatePort(Dictionary<string, string> dic, out string error)
{
error = string.Empty;
//界面接口
if (dic.ContainsKey("web") == false || string.IsNullOrWhiteSpace(dic["web"]))
{
dic["web"] = "1800";
}
//管理接口
if (dic.ContainsKey("api") == false || string.IsNullOrWhiteSpace(dic["api"]))
{
dic["api"] = "1801";
}
//服务接口
if (dic.ContainsKey("service") == false || string.IsNullOrWhiteSpace(dic["service"]))
{
dic["service"] = "1802";
}
return true;
}
static bool ValidateMemoryKey(Dictionary<string, string> dic, out string error)
{
error = string.Empty;
if (dic.ContainsKey("username-key") == false || string.IsNullOrWhiteSpace(dic["username-key"]))
{
dic["username-key"] = "cmonitor/username";
}
if (dic.ContainsKey("username-len") == false || string.IsNullOrWhiteSpace(dic["username-len"]))
{
dic["username-len"] = "255";
}
if (dic.ContainsKey("keyboard-key") == false || string.IsNullOrWhiteSpace(dic["keyboard-key"]))
{
dic["keyboard-key"] = "cmonitor/keyboard";
}
if (dic.ContainsKey("keyboard-len") == false || string.IsNullOrWhiteSpace(dic["keyboard-len"]))
{
dic["keyboard-len"] = "255";
}
if (dic.ContainsKey("share-key") == false || string.IsNullOrWhiteSpace(dic["share-key"]))
{
dic["share-key"] = "cmonitor/share";
}
if (dic.ContainsKey("share-len") == false || string.IsNullOrWhiteSpace(dic["share-len"]))
{
dic["share-len"] = "1024";
}
return true;
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net7.0\publish\win-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<History>True|2023-09-04T10:19:54.7492652Z;True|2023-09-04T18:19:32.2969345+08:00;False|2023-09-04T18:18:51.7827366+08:00;True|2023-09-04T18:15:31.6783417+08:00;True|2023-09-04T18:14:40.9964104+08:00;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

79
cmonitor/app.manifest Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

68
cmonitor/cmonitor.csproj Normal file
View File

@@ -0,0 +1,68 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<Configurations>Debug;Release;ReleaseLinux</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLinux|AnyCPU'">
<DebugType>embedded</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common.libs\common.libs.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="nfapi.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="nfdriver.sys">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="./web/**">
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Remove="Properties\**" />
<Content Remove="Properties\**" />
<EmbeddedResource Remove="Properties\**" />
<None Remove="Properties\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MemoryPack" Version="1.9.16" />
<PackageReference Include="NAudio" Version="2.2.0" />
<PackageReference Include="System.Management" Version="7.0.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>D:\desktop\cmonitor\cmonitor\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

BIN
cmonitor/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,36 @@
namespace cmonitor.hijack
{
public sealed class HijackConfig
{
public HijackConfig() { }
/// <summary>
/// 进程白名单
/// </summary>
public string[] AllowProcesss { get; set; } = Array.Empty<string>();
/// <summary>
/// 进程黑名单
/// </summary>
public string[] DeniedProcesss { get; set; } = Array.Empty<string>();
/// <summary>
/// 域名白名单
/// </summary>
public string[] AllowDomains { get; set; } = Array.Empty<string>();
/// <summary>
/// 域名黑名单
/// </summary>
public string[] DeniedDomains { get; set; } = Array.Empty<string>();
/// <summary>
/// ip白名单
/// </summary>
public string[] AllowIPs { get; set; } = Array.Empty<string>();
/// <summary>
/// ip黑名单
/// </summary>
public string[] DeniedIPs { get; set; } = Array.Empty<string>();
}
}

View File

@@ -0,0 +1,321 @@
using common.libs;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace cmonitor.hijack;
public sealed class HijackController
{
private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys";
public const string NFDriver = "nfdriver.sys";
public const string Name = "netfilter2";
private readonly HijackConfig hijackConfig;
private readonly HijackEventHandler hijackEventHandler;
public HijackController(HijackConfig hijackConfig, HijackEventHandler hijackEventHandler)
{
this.hijackConfig = hijackConfig;
this.hijackEventHandler = hijackEventHandler;
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Stop();
Console.CancelKeyPress += (sender, e) => Stop();
}
public bool Start()
{
Stop();
//检查安装驱动
CheckDriver();
//给驱动获取进程权限
NFAPI.nf_adjustProcessPriviledges();
//初始化驱动
NF_STATUS nF_STATUS = NFAPI.nf_init(Name, hijackEventHandler);
if (nF_STATUS != NF_STATUS.NF_STATUS_SUCCESS)
{
throw new Exception($"{Name} start failed.{nF_STATUS}");
}
SetRules();
return true;
}
public void Stop()
{
try
{
NFAPI.nf_deleteRules();
NFAPI.nf_free();
}
catch (Exception)
{
}
}
public void SetRules()
{
List<NF_RULE> rules = new List<NF_RULE>();
Filter53(rules);
FilterIPV6Lan(rules);
FilterIPV4Lan(rules);
FilterConfigIPs(rules);
FilterWan(rules);
NFAPI.nf_setRules(rules.ToArray());
}
private void Filter53(List<NF_RULE> rules)
{
rules.AddRange(new NF_RULE[] {
//TCP 53
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_INDICATE_CONNECT_REQUESTS,
protocol = (int)ProtocolType.Tcp,
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
ip_family = (ushort)AddressFamily.InterNetwork
},
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_INDICATE_CONNECT_REQUESTS,
protocol = (int)ProtocolType.Tcp,
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
ip_family = (ushort)AddressFamily.InterNetworkV6
},
//UDP 53
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Udp,
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
ip_family = (ushort)AddressFamily.InterNetwork
},
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Udp,
remotePort = BinaryPrimitives.ReverseEndianness((ushort)53),
ip_family = (ushort)AddressFamily.InterNetworkV6
}
});
}
private void FilterIPV6Lan(List<NF_RULE> rules)
{
rules.AddRange(new NF_RULE[]
{
//IPV6 环回 ::1/128
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetworkV6,
remoteIpAddress = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
remoteIpAddressMask = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
},
//IPV6 组播 FF00::/8
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetworkV6,
remoteIpAddress = new byte[] { 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
remoteIpAddressMask = new byte[] { 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
},
//本地链路 FE80::/10
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetworkV6,
remoteIpAddress = new byte[] { 0xFE, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
remoteIpAddressMask = new byte[] { 255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
},
//本地站点 FEC0::/10
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetworkV6,
remoteIpAddress = new byte[] { 0xFE, 0xC0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
remoteIpAddressMask = new byte[] { 255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
}
});
}
private void FilterIPV4Lan(List<NF_RULE> rules)
{
List<string> intranetIpv4s = new List<string>() {
"10.0.0.0/8", "100.64.0.0/10",
"127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
"192.0.0.0/24", "192.0.2.0/24","192.88.99.0/24","192.168.0.0/16",
"198.18.0.0/15","198.51.100.0/24",
"203.0.113.0/24","224.0.0.0/4", "240.0.0.0/4","255.255.255.255/32"
};
foreach (string item in intranetIpv4s)
{
string[] arr = item.Split('/');
rules.Add(new NF_RULE
{
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetwork,
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
});
}
}
private void FilterConfigIPs(List<NF_RULE> rules)
{
foreach (string item in hijackConfig.DeniedIPs)
{
string[] arr = item.Split('/');
rules.Add(new NF_RULE
{
filteringFlag = (uint)NF_FILTERING_FLAG.NF_BLOCK,
ip_family = (ushort)AddressFamily.InterNetwork,
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
});
}
foreach (string item in hijackConfig.AllowIPs)
{
string[] arr = item.Split('/');
rules.Add(new NF_RULE
{
filteringFlag = (uint)NF_FILTERING_FLAG.NF_ALLOW,
ip_family = (ushort)AddressFamily.InterNetwork,
remoteIpAddress = IPAddress.Parse(arr[0]).GetAddressBytes(),
remoteIpAddressMask = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(0xffffffff << 32 - byte.Parse(arr[1]))),
});
}
}
private void FilterWan(List<NF_RULE> rules)
{
rules.AddRange(new List<NF_RULE> {
//TCP
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Tcp,
ip_family = (ushort)AddressFamily.InterNetwork
},
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Tcp,
ip_family = (ushort)AddressFamily.InterNetworkV6
},
//UDP
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Udp,
ip_family = (ushort)AddressFamily.InterNetwork
},
new NF_RULE
{
direction = (byte)NF_DIRECTION.NF_D_OUT,
filteringFlag = (uint)NF_FILTERING_FLAG.NF_FILTER,
protocol = (int)ProtocolType.Udp,
ip_family = (ushort)AddressFamily.InterNetworkV6
},
});
}
private string GetFileVersion(string file)
{
if (File.Exists(file))
return FileVersionInfo.GetVersionInfo(file).FileVersion ?? "";
return "";
}
private void CheckDriver()
{
var binFileVersion = GetFileVersion(NFDriver);
var systemFileVersion = GetFileVersion(SystemDriver);
if (File.Exists(SystemDriver) == false)
{
// Install
InstallDriver();
return;
}
var reinstall = false;
if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
{
if (binResult.CompareTo(systemResult) > 0)
// Update
reinstall = true;
else if (systemResult.Major != binResult.Major)
// Downgrade when Major version different (may have breaking changes)
reinstall = true;
}
else
{
// Parse File versionName to Version failed
if (!systemFileVersion.Equals(binFileVersion))
// versionNames are different, Reinstall
reinstall = true;
}
if (!reinstall)
return;
UninstallDriver();
InstallDriver();
}
private void InstallDriver()
{
if (!File.Exists(NFDriver))
throw new Exception("builtin driver files missing, can't install NF driver");
try
{
File.Copy(NFDriver, SystemDriver);
}
catch (Exception e)
{
throw new Exception($"Copy {Name}.sys failed\n{e.Message}");
}
// 注册驱动文件
if (NFAPI.nf_registerDriver(Name) == NF_STATUS.NF_STATUS_SUCCESS)
{
Logger.Instance.Debug($"Install {Name} driver finished");
}
else
{
throw new Exception($"Register {Name} failed");
}
}
private bool UninstallDriver()
{
Stop();
if (File.Exists(SystemDriver) == false)
return true;
NFAPI.nf_unRegisterDriver(Name);
File.Delete(SystemDriver);
return true;
}
}

View File

@@ -0,0 +1,236 @@
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Text;
using common.libs.extends;
using System.IO;
namespace cmonitor.hijack
{
public sealed class HijackEventHandler : NF_EventHandler
{
private readonly HijackConfig hijackConfig;
private readonly uint currentProcessId = 0;
private readonly ConcurrentDictionary<ulong, bool> udpConnections = new ConcurrentDictionary<ulong, bool>();
private readonly ConcurrentDictionary<ulong, bool> tcpConnections = new ConcurrentDictionary<ulong, bool>();
public ulong UdpSend { get; private set; }
public ulong UdpReceive { get; private set; }
public ulong TcpSend { get; private set; }
public ulong TcpReceive { get; private set; }
public HijackEventHandler(HijackConfig hijackConfig)
{
this.hijackConfig = hijackConfig;
currentProcessId = (uint)Process.GetCurrentProcess().Id;
}
#region tcp无需处理
public void tcpCanReceive(ulong id)
{
}
public void tcpCanSend(ulong id)
{
}
public void tcpConnectRequest(ulong id, ref NF_TCP_CONN_INFO pConnInfo)
{
}
public void tcpConnected(ulong id, NF_TCP_CONN_INFO pConnInfo)
{
//是阻止进程
if (checkProcess(pConnInfo.processId, out string processName))
{
return;
}
tcpConnections.TryAdd(id, true);
return;
}
public void tcpSend(ulong id, IntPtr buf, int len)
{
if (tcpConnections.ContainsKey(id))
{
TcpSend += (ulong)len;
NFAPI.nf_tcpPostSend(id, buf, len);
}
}
public void tcpReceive(ulong id, IntPtr buf, int len)
{
TcpReceive += (ulong)len;
NFAPI.nf_tcpPostReceive(id, buf, len);
}
public void tcpClosed(ulong id, NF_TCP_CONN_INFO pConnInfo)
{
tcpConnections.TryRemove(id, out _);
}
#endregion
#region udp无需处理
public void udpCanReceive(ulong id)
{
}
public void udpCanSend(ulong id)
{
}
public void udpConnectRequest(ulong id, ref NF_UDP_CONN_REQUEST pConnReq)
{
}
public void threadEnd()
{
}
public void threadStart()
{
}
public void udpReceive(ulong id, nint remoteAddress, nint buf, int len, nint options, int optionsLen)
{
UdpReceive += (ulong)len;
NFAPI.nf_udpPostReceive(id, remoteAddress, buf, len, options);
}
#endregion
public void udpClosed(ulong id, NF_UDP_CONN_INFO pConnInfo)
{
udpConnections.TryRemove(id, out _);
//删除udp对象缓存
}
public void udpCreated(ulong id, NF_UDP_CONN_INFO pConnInfo)
{
//是阻止进程
if (checkProcess(pConnInfo.processId, out string processName))
{
return;
}
udpConnections.TryAdd(id, true);
}
public unsafe void udpSend(ulong id, nint remoteAddress, nint buf, int len, nint options, int optionsLen)
{
//丢弃进程包
if (udpConnections.TryGetValue(id, out _) == false)
{
return;
}
//丢弃域名包
if (checkDomain(remoteAddress, buf, len))
{
return;
}
UdpSend += (ulong)len;
NFAPI.nf_udpPostSend(id, remoteAddress, buf, len, options);
}
/// <summary>
/// 是否阻止域名
/// </summary>
/// <param name="remoteAddress"></param>
/// <param name="buf"></param>
/// <param name="len"></param>
/// <returns></returns>
private unsafe bool checkDomain(nint remoteAddress, nint buf, int len)
{
if (hijackConfig.AllowDomains.Length == 0 && hijackConfig.DeniedDomains.Length == 0)
{
return false;
}
byte* p = (byte*)remoteAddress;
ushort port = (ushort)(*(p + 2) << 8 & 0xFF00 | *(p + 3));
if (port == 53)
{
try
{
Span<byte> span = new Span<byte>((void*)buf, len);
span = span.Slice(4);
ushort length = (ushort)(span[0] << 8 | span[1]);
span = span.Slice(8);
for (int i = 0; i < length; i++)
{
StringBuilder sb = new StringBuilder(256);
while (span[0] > 0)
{
sb.Append(span.Slice(1, span[0]).GetString());
sb.Append(".");
span = span.Slice(1 + span[0]);
}
string domain = (sb.ToString(0, sb.Length - 1));
if (checkDomain(domain))
{
return true;
}
}
}
catch (Exception)
{
return true;
}
}
return false;
}
private bool checkDomain(string domain)
{
//黑名单
if (hijackConfig.DeniedDomains.Length > 0 && checkName(hijackConfig.DeniedDomains, domain))
{
return true;
}
//白名单
if (hijackConfig.AllowDomains.Length > 0)
{
return checkName(hijackConfig.AllowDomains, domain) == false;
}
return false;
}
/// <summary>
/// 是否阻止进程
/// </summary>
/// <param name="processId"></param>
/// <param name="processName"></param>
/// <returns></returns>
private bool checkProcess(uint processId, out string processName)
{
processName = string.Empty;
if (currentProcessId == processId)
{
return false;
}
processName = NFAPI.nf_getProcessName(processId);
//黑名单
if (hijackConfig.DeniedProcesss.Length > 0 && checkName(hijackConfig.DeniedProcesss, processName))
{
return true;
}
//白名单
if (hijackConfig.AllowProcesss.Length > 0)
{
return checkName(hijackConfig.AllowProcesss, processName) == false;
}
return false;
}
private bool checkName(string[] names, string path)
{
for (int i = 0; i < names.Length; i++)
{
if (names[i].Length > path.Length) continue;
var pathSpan = path.AsSpan();
var nameSpan = names[i].AsSpan();
try
{
if (pathSpan.Slice(pathSpan.Length - nameSpan.Length, nameSpan.Length).SequenceEqual(nameSpan))
{
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex + "");
}
}
return false;
}
}
}

1042
cmonitor/hijack/NFApi.cs Normal file

File diff suppressed because it is too large Load Diff

BIN
cmonitor/nfapi.dll Normal file

Binary file not shown.

BIN
cmonitor/nfdriver.sys Normal file

Binary file not shown.

View File

@@ -0,0 +1,314 @@
using cmonitor.server.api.websocket;
using common.libs;
using common.libs.extends;
using Microsoft.Extensions.DependencyInjection;
using System.Buffers;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text.Json;
namespace cmonitor.server.api
{
/// <summary>
/// 前段接口服务
/// </summary>
public sealed class ClientServer : IClientServer
{
private readonly Dictionary<string, PluginPathCacheInfo> plugins = new();
private readonly ConcurrentDictionary<uint, ConnectionTimeInfo> connectionTimes = new();
public uint OnlineNum = 0;
private readonly ServiceProvider serviceProvider;
private WebSocketServer server;
private readonly Config config;
public ClientServer(ServiceProvider serviceProvider, Config config)
{
this.serviceProvider = serviceProvider;
this.config = config;
}
/// <summary>
/// 加载插件
/// </summary>
/// <param name="assemblys"></param>
public void LoadPlugins(Assembly[] assemblys)
{
Type voidType = typeof(void);
IEnumerable<Type> types = assemblys.SelectMany(c => c.GetTypes());
foreach (Type item in types.Where(c => c.GetInterfaces().Contains(typeof(IClientService))))
{
string path = item.Name.Replace("ClientService", "");
object obj = serviceProvider.GetService(item);
foreach (MethodInfo method in item.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
string key = $"{path}/{method.Name}".ToLower();
if (!plugins.ContainsKey(key))
{
bool istask = method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null;
bool isTaskResult = method.ReturnType.GetProperty("Result") != null;
plugins.TryAdd(key, new PluginPathCacheInfo
{
IsVoid = method.ReturnType == voidType,
Method = method,
Target = obj,
IsTask = istask,
IsTaskResult = isTaskResult
});
}
}
}
}
/// <summary>
/// 开启websockt
/// </summary>
public void Websocket()
{
server = new WebSocketServer();
try
{
server.Start(System.Net.IPAddress.Any, config.ApiPort);
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
server.OnOpen = (connection) =>
{
Interlocked.Increment(ref OnlineNum);
connectionTimes.TryAdd(connection.Id, new ConnectionTimeInfo());
};
server.OnDisConnectd = (connection) =>
{
Interlocked.Decrement(ref OnlineNum);
if (OnlineNum < 0) Interlocked.Exchange(ref OnlineNum, 0);
connectionTimes.TryRemove(connection.Id, out _);
};
server.OnMessage = (connection, frame, message) =>
{
if (connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo))
{
timeInfo.DateTime = DateTime.Now;
}
var req = message.DeJson<ClientServiceRequestInfo>();
req.Connection = connection;
OnMessage(req).ContinueWith((result) =>
{
var resp = result.Result.ToJson().ToBytes();
connection.SendFrameText(resp);
});
};
}
/// <summary>
/// 收到消息
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<ClientServiceResponseInfo> OnMessage(ClientServiceRequestInfo model)
{
model.Path = model.Path.ToLower();
if (plugins.TryGetValue(model.Path, out PluginPathCacheInfo plugin) == false)
{
return new ClientServiceResponseInfo
{
Content = "not exists this path",
RequestId = model.RequestId,
Path = model.Path,
Code = ClientServiceResponseCodes.NotFound
};
}
try
{
ClientServiceParamsInfo param = new ClientServiceParamsInfo
{
RequestId = model.RequestId,
Content = model.Content,
Connection = model.Connection
};
dynamic resultAsync = plugin.Method.Invoke(plugin.Target, new object[] { param });
object resultObject = null;
if (plugin.IsVoid == false)
{
if (plugin.IsTask)
{
await resultAsync.ConfigureAwait(false);
if (plugin.IsTaskResult)
{
resultObject = resultAsync.Result;
}
}
else
{
resultObject = resultAsync;
}
}
return new ClientServiceResponseInfo
{
Code = param.Code,
Content = param.Code != ClientServiceResponseCodes.Error ? resultObject : param.ErrorMessage,
RequestId = model.RequestId,
Path = model.Path,
};
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
return new ClientServiceResponseInfo
{
Content = ex.Message,
RequestId = model.RequestId,
Path = model.Path,
Code = ClientServiceResponseCodes.Error
};
}
}
public void Notify(string path, object content)
{
if (server.Connections.Any())
{
try
{
byte[] bytes = JsonSerializer.Serialize(new ClientServiceResponseInfo
{
Code = ClientServiceResponseCodes.Success,
Content = content,
Path = path,
RequestId = 0
}).ToBytes();
foreach (WebsocketConnection connection in server.Connections)
{
if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000)
{
try
{
connection.SendFrameText(bytes);
}
catch (Exception)
{
}
}
}
}
catch (Exception)
{
//Logger.Instance.Error(ex);
}
}
}
public void Notify(string path, string name, Memory<byte> content)
{
if (server.Connections.Any())
{
try
{
Memory<byte> headMemory = JsonSerializer.Serialize(new ClientServiceResponseInfo
{
Code = ClientServiceResponseCodes.Success,
Content = name,
Path = path,
RequestId = 0
}).ToBytes();
int length = 4 + headMemory.Length + content.Length;
byte[] result = ArrayPool<byte>.Shared.Rent(length);
int index = 0;
headMemory.Length.ToBytes(result);
index += 4;
headMemory.CopyTo(result.AsMemory(index));
index += headMemory.Length;
content.CopyTo(result.AsMemory(index));
index += content.Length;
foreach (WebsocketConnection connection in server.Connections)
{
if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000)
{
try
{
connection.SendFrameBinary(result.AsMemory(0, length));
}
catch (Exception)
{
}
}
}
ArrayPool<byte>.Shared.Return(result);
}
catch (Exception)
{
//Logger.Instance.Error(ex);
}
}
}
public void Notify(string path, object content, WebsocketConnection connection)
{
try
{
if (connection.Connected == false) return;
byte[] bytes = JsonSerializer.Serialize(new ClientServiceResponseInfo
{
Code = ClientServiceResponseCodes.Success,
Content = content,
Path = path,
RequestId = 0
}).ToBytes();
try
{
connection.SendFrameText(bytes);
}
catch (Exception)
{
}
}
catch (Exception)
{
//Logger.Instance.Error(ex);
}
}
}
public sealed class ConnectionTimeInfo
{
public DateTime DateTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 前段接口缓存
/// </summary>
public struct PluginPathCacheInfo
{
/// <summary>
/// 对象
/// </summary>
public object Target { get; set; }
/// <summary>
/// 方法
/// </summary>
public MethodInfo Method { get; set; }
/// <summary>
/// 是否void
/// </summary>
public bool IsVoid { get; set; }
/// <summary>
/// 是否task
/// </summary>
public bool IsTask { get; set; }
/// <summary>
/// 是否task result
/// </summary>
public bool IsTaskResult { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using cmonitor.server.api.websocket;
using System.Reflection;
namespace cmonitor.server.api
{
/// <summary>
/// 前端接口服务
/// </summary>
public interface IClientServer
{
/// <summary>
/// websocket
/// </summary>
public void Websocket();
/// <summary>
/// 加载插件
/// </summary>
/// <param name="assemblys"></param>
public void LoadPlugins(Assembly[] assemblys);
public void Notify(string path, object content);
public void Notify(string path,string name, Memory<byte> content);
public void Notify(string path, object content,WebsocketConnection connection);
}
}

View File

@@ -0,0 +1,133 @@
using cmonitor.server.api.websocket;
using System.Text.Json.Serialization;
namespace cmonitor.server.api
{
/// <summary>
/// 前段接口
/// </summary>
public interface IClientService { }
/// <summary>
/// 前段接口response
/// </summary>
public sealed class ClientServiceResponseInfo
{
/// <summary>
/// 路径
/// </summary>
public string Path { get; set; } = string.Empty;
/// <summary>
/// 请求id
/// </summary>
public long RequestId { get; set; } = 0;
/// <summary>
/// 状态码
/// </summary>
public ClientServiceResponseCodes Code { get; set; } = ClientServiceResponseCodes.Success;
/// <summary>
/// 数据
/// </summary>
public object Content { get; set; } = string.Empty;
}
/// <summary>
/// 前端接口request
/// </summary>
public sealed class ClientServiceRequestInfo
{
[JsonIgnore]
public WebsocketConnection Connection { get; set; }
/// <summary>
/// 路径
/// </summary>
public string Path { get; set; } = string.Empty;
/// <summary>
/// 请求id
/// </summary>
public uint RequestId { get; set; } = 0;
/// <summary>
/// 数据
/// </summary>
public string Content { get; set; } = string.Empty;
}
/// <summary>
/// 前端接口执行参数
/// </summary>
public sealed class ClientServiceParamsInfo
{
public WebsocketConnection Connection { get; set; }
/// <summary>
/// 请求id
/// </summary>
public uint RequestId { get; set; } = 0;
/// <summary>
/// 数据
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// 状态码
/// </summary>
public ClientServiceResponseCodes Code { get; private set; } = ClientServiceResponseCodes.Success;
/// <summary>
/// 错误信息
/// </summary>
public string ErrorMessage { get; private set; } = string.Empty;
/// <summary>
/// 设置状态码
/// </summary>
/// <param name="code"></param>
/// <param name="errormsg"></param>
public void SetCode(ClientServiceResponseCodes code, string errormsg = "")
{
Code = code;
ErrorMessage = errormsg;
}
/// <summary>
/// 设置错误信息
/// </summary>
/// <param name="msg"></param>
public void SetErrorMessage(string msg)
{
Code = ClientServiceResponseCodes.Error;
ErrorMessage = msg;
}
}
/// <summary>
/// 前端接口状态码
/// </summary>
public enum ClientServiceResponseCodes : byte
{
/// <summary>
/// 成功
/// </summary>
Success = 0,
/// <summary>
/// 没找到
/// </summary>
NotFound = 1,
/// <summary>
/// 失败
/// </summary>
Error = 0xff,
}
/// <summary>
/// 前端接口标识特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ClientServiceAttribute : Attribute
{
/// <summary>
/// 参数类型
/// </summary>
public Type Param { get; set; }
public ClientServiceAttribute(Type param)
{
Param = param;
}
}
}

View File

@@ -0,0 +1,386 @@
using common.libs.database;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace cmonitor.server.api
{
[Table("rule")]
public sealed class RuleConfig
{
private readonly IConfigDataProvider<RuleConfig> configDataProvider;
public RuleConfig() { }
public RuleConfig(IConfigDataProvider<RuleConfig> configDataProvider)
{
this.configDataProvider = configDataProvider;
RuleConfig config = configDataProvider.Load().Result ?? new RuleConfig
{
UserNames = new Dictionary<string, UserNameInfo> { { "snltty", new UserNameInfo {
Rules = new List<RulesInfo>{ new RulesInfo { ID = 1, Name = "默认" } },
Processs = new List<GroupInfo>{ new GroupInfo { ID = 1, Name = "默认" } },
} } }
};
UserNames = config.UserNames;
MaxID = config.MaxID;
Save();
}
public Dictionary<string, UserNameInfo> UserNames { get; set; } = new Dictionary<string, UserNameInfo>();
private uint maxid = 0;
public uint MaxID
{
get => maxid; set
{
maxid = value;
}
}
private readonly object lockObj = new object();
public string AddName(string name)
{
lock (lockObj)
{
if (UserNames.ContainsKey(name) == false)
{
UserNames.Add(name, new UserNameInfo
{
Rules = new List<RulesInfo> { new RulesInfo { ID = 1, Name = "默认" } },
Processs = new List<GroupInfo> { new GroupInfo { ID = 1, Name = "默认" } },
});
}
Save();
}
return string.Empty;
}
public string AddProcessGroup(UpdateGroupInfo updateGroupInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(updateGroupInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
if (userNameInfo.Processs.FirstOrDefault(c => c.Name == updateGroupInfo.Group.Name && c.ID != updateGroupInfo.Group.ID) != null)
{
return "已存在同名记录";
}
//添加
if (updateGroupInfo.Group.ID == 0)
{
updateGroupInfo.Group.ID = Interlocked.Increment(ref maxid);
userNameInfo.Processs.Add(updateGroupInfo.Group);
Save();
return string.Empty;
}
//修改
GroupInfo old = userNameInfo.Processs.FirstOrDefault(c => c.ID == updateGroupInfo.Group.ID);
if (old == null)
{
return "不存在记录,无法修改";
}
old.Name = updateGroupInfo.Group.Name;
Save();
}
return string.Empty;
}
public string AddProcess(UpdateItemInfo updateItem)
{
lock (lockObj)
{
if (UserNames.TryGetValue(updateItem.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
GroupInfo group = userNameInfo.Processs.FirstOrDefault(c => c.ID == updateItem.GroupID);
if (group == null)
{
return "不存在此分组";
}
if (group.List.FirstOrDefault(c => c.Name == updateItem.Item.Name && c.ID != updateItem.Item.ID) != null)
{
return "已存在同名记录";
}
//添加
if (updateItem.Item.ID == 0)
{
updateItem.Item.ID = Interlocked.Increment(ref maxid);
group.List.Add(updateItem.Item);
Save();
return string.Empty;
}
//修改
ItemInfo old = group.List.FirstOrDefault(c => c.ID == updateItem.Item.ID);
if (old == null)
{
return "不存在记录,无法修改";
}
old.Name = updateItem.Item.Name;
old.AllowType = updateItem.Item.AllowType;
old.DataType = updateItem.Item.DataType;
Save();
}
return string.Empty;
}
public string DeleteProcessGroup(DeleteGroupInfo deleteGroupInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(deleteGroupInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
userNameInfo.Processs.Remove(userNameInfo.Processs.FirstOrDefault(c => c.ID == deleteGroupInfo.ID));
Save();
}
return string.Empty;
}
public string DeleteProcess(DeleteItemInfo deleteItemInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(deleteItemInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
GroupInfo group = userNameInfo.Processs.FirstOrDefault(c => c.ID == deleteItemInfo.GroupID);
if (group == null)
{
return "不存在此分组";
}
group.List.Remove(group.List.FirstOrDefault(c => c.ID == deleteItemInfo.ID));
}
return string.Empty;
}
public string AddRule(UpdateRuleInfo updateRuleInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(updateRuleInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
if (userNameInfo.Rules.FirstOrDefault(c => c.Name == updateRuleInfo.Rule.Name && c.ID != updateRuleInfo.Rule.ID) != null)
{
return "已存在同名记录";
}
//添加
if (updateRuleInfo.Rule.ID == 0)
{
updateRuleInfo.Rule.ID = Interlocked.Increment(ref maxid);
userNameInfo.Rules.Add(updateRuleInfo.Rule);
Save();
return string.Empty;
}
//修改
RulesInfo old = userNameInfo.Rules.FirstOrDefault(c => c.ID == updateRuleInfo.Rule.ID);
if (old == null)
{
return "不存在记录,无法修改";
}
old.Name = updateRuleInfo.Rule.Name;
old.PrivateProcesss = updateRuleInfo.Rule.PrivateProcesss;
old.PublicProcesss = updateRuleInfo.Rule.PublicProcesss;
Save();
}
return string.Empty;
}
public string DeleteRule(DeleteRuleInfo deleteRuleInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(deleteRuleInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
userNameInfo.Rules.Remove(userNameInfo.Rules.FirstOrDefault(c => c.ID == deleteRuleInfo.ID));
Save();
}
return string.Empty;
}
public string UpdateDevices(UpdateDevicesInfo updatDevicesInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(updatDevicesInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
userNameInfo.Devices = updatDevicesInfo.Devices;
Save();
}
return string.Empty;
}
public string AddFileName(AddFileNameInfo addFileNameInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(addFileNameInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
if (userNameInfo.FileNames.FirstOrDefault(c => c.FileName == addFileNameInfo.FileName.FileName && c.ID != addFileNameInfo.FileName.ID) != null)
{
return "已存在同名记录";
}
//添加
if (addFileNameInfo.FileName.ID == 0)
{
addFileNameInfo.FileName.ID = Interlocked.Increment(ref maxid);
userNameInfo.FileNames.Add(addFileNameInfo.FileName);
Save();
return string.Empty;
}
//修改
FileNameInfo old = userNameInfo.FileNames.FirstOrDefault(c => c.ID == addFileNameInfo.FileName.ID);
if (old == null)
{
return "不存在记录,无法修改";
}
old.FileName = addFileNameInfo.FileName.FileName;
old.Desc = addFileNameInfo.FileName.Desc;
Save();
}
return string.Empty;
}
public string DelFileName(DeletedFileNameInfo deletedFileNameInfo)
{
lock (lockObj)
{
if (UserNames.TryGetValue(deletedFileNameInfo.UserName, out UserNameInfo userNameInfo) == false)
{
return "不存在此管理用户";
}
userNameInfo.FileNames.Remove(userNameInfo.FileNames.FirstOrDefault(c=>c.ID == deletedFileNameInfo.ID));
Save();
}
return string.Empty;
}
public void Save()
{
configDataProvider.Save(this).Wait();
}
}
public sealed class UserNameInfo
{
public List<RulesInfo> Rules { get; set; } = new List<RulesInfo>();
public List<GroupInfo> Processs { get; set; } = new List<GroupInfo>();
public List<string> Devices { get; set; } = new List<string>();
public List<FileNameInfo> FileNames { get; set; } = new List<FileNameInfo>();
}
public sealed class UpdateDevicesInfo
{
public string UserName { get; set; }
public List<string> Devices { get; set; } = new List<string>();
}
public sealed class RulesInfo
{
public uint ID { get; set; }
public string Name { get; set; }
public List<uint> PrivateProcesss { get; set; } = new List<uint>();
public List<uint> PublicProcesss { get; set; } = new List<uint>();
}
public sealed class UpdateRuleInfo
{
public string UserName { get; set; }
public RulesInfo Rule { get; set; }
}
public sealed class DeleteRuleInfo
{
public string UserName { get; set; }
public uint ID { get; set; }
}
public sealed class GroupInfo
{
public uint ID { get; set; }
public string Name { get; set; }
public List<ItemInfo> List { get; set; } = new List<ItemInfo>();
}
public sealed class UpdateGroupInfo
{
public string UserName { get; set; }
public GroupInfo Group { get; set; }
}
public sealed class DeleteGroupInfo
{
public string UserName { get; set; }
public uint ID { get; set; }
}
public sealed class ItemInfo
{
public uint ID { get; set; }
public string Name { get; set; }
public DataType DataType { get; set; }
public AllowType AllowType { get; set; }
}
public enum DataType
{
Process = 0,
Domain = 1,
IP = 2,
}
public enum AllowType
{
Allow = 0,
Denied = 1
}
public sealed class UpdateItemInfo
{
public string UserName { get; set; }
public uint GroupID { get; set; }
public ItemInfo Item { get; set; }
}
public sealed class DeleteItemInfo
{
public string UserName { get; set; }
public uint GroupID { get; set; }
public uint ID { get; set; }
}
public sealed class FileNameInfo
{
public uint ID { get; set; }
public string FileName { get; set; }
public string Desc { get; set; }
}
public sealed class AddFileNameInfo
{
public string UserName { get; set; }
public FileNameInfo FileName { get; set; }
}
public sealed class DeletedFileNameInfo
{
public string UserName { get; set; }
public uint ID { get; set; }
}
}

View File

@@ -0,0 +1,87 @@
using cmonitor.server.client.reports.active;
using cmonitor.server.service;
using cmonitor.server.service.messengers.active;
using cmonitor.server.service.messengers.sign;
using common.libs;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class ActiveClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
private readonly RuleConfig ruleConfig;
public ActiveClientService(MessengerSender messengerSender, SignCaching signCaching, RuleConfig ruleConfig)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
this.ruleConfig = ruleConfig;
}
public async Task<ActiveWindowTimeReportInfo> Get(ClientServiceParamsInfo param)
{
if (signCaching.Get(param.Content, out SignCacheInfo cache) && cache.Connected)
{
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ActiveMessengerIds.Get
});
if (resp.Code == MessageResponeCodes.OK)
{
return MemoryPackSerializer.Deserialize<ActiveWindowTimeReportInfo>(resp.Data.Span);
}
}
return new ActiveWindowTimeReportInfo();
}
public async Task<bool> Clear(ClientServiceParamsInfo param)
{
if (signCaching.Get(param.Content, out SignCacheInfo cache) && cache.Connected)
{
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ActiveMessengerIds.Clear
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
return false;
}
public async Task<bool> Disallow(ClientServiceParamsInfo param)
{
DisallowInfo disallowInfo = param.Content.DeJson<DisallowInfo>();
byte[] bytes = MemoryPackSerializer.Serialize(disallowInfo.FileNames);
foreach (string name in disallowInfo.Names)
{
if (signCaching.Get(name, out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ActiveMessengerIds.Disallow,
Payload = bytes
});
}
}
return false;
}
public string Add(ClientServiceParamsInfo param)
{
return ruleConfig.AddFileName(param.Content.DeJson<AddFileNameInfo>());
}
public string Del(ClientServiceParamsInfo param)
{
return ruleConfig.DelFileName(param.Content.DeJson<DeletedFileNameInfo>());
}
}
public sealed class DisallowInfo
{
public string[] Names { get; set; }
public string[] FileNames { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.command;
using cmonitor.server.service.messengers.sign;
using common.libs;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class CommandClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
private readonly IClientServer clientServer;
public CommandClientService(MessengerSender messengerSender, SignCaching signCaching, IClientServer clientServer)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
this.clientServer = clientServer;
}
public async Task<bool> Exec(ClientServiceParamsInfo param)
{
ExecInfo info = param.Content.DeJson<ExecInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)CommandMessengerIds.Exec,
Payload = MemoryPackSerializer.Serialize(info.Commands)
});
}
}
return true;
}
}
public sealed class ExecInfo
{
public string[] Names { get; set; }
public string[] Commands { get; set; }
}
}

View File

@@ -0,0 +1,95 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.hijack;
using cmonitor.server.service.messengers.sign;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class HijackClientService : IClientService
{
private readonly RuleConfig ruleConfig;
private readonly SignCaching signCaching;
private readonly MessengerSender messengerSender;
public HijackClientService(RuleConfig ruleConfig, SignCaching signCaching, MessengerSender messengerSender)
{
this.ruleConfig = ruleConfig;
this.signCaching = signCaching;
this.messengerSender = messengerSender;
}
public Dictionary<string, UserNameInfo> Info(ClientServiceParamsInfo param)
{
return ruleConfig.UserNames;
}
public string AddName(ClientServiceParamsInfo param)
{
return ruleConfig.AddName(param.Content);
}
public string AddProcessGroup(ClientServiceParamsInfo param)
{
return ruleConfig.AddProcessGroup(param.Content.DeJson<UpdateGroupInfo>());
}
public string DeleteProcessGroup(ClientServiceParamsInfo param)
{
return ruleConfig.DeleteProcessGroup(param.Content.DeJson<DeleteGroupInfo>());
}
public string AddProcess(ClientServiceParamsInfo param)
{
return ruleConfig.AddProcess(param.Content.DeJson<UpdateItemInfo>());
}
public string DeleteProcess(ClientServiceParamsInfo param)
{
return ruleConfig.DeleteProcess(param.Content.DeJson<DeleteItemInfo>());
}
public string AddRule(ClientServiceParamsInfo param)
{
return ruleConfig.AddRule(param.Content.DeJson<UpdateRuleInfo>());
}
public string DeleteRule(ClientServiceParamsInfo param)
{
return ruleConfig.DeleteRule(param.Content.DeJson<DeleteRuleInfo>());
}
public string UpdateDevices(ClientServiceParamsInfo param)
{
return ruleConfig.UpdateDevices(param.Content.DeJson<UpdateDevicesInfo>());
}
public async Task<List<string>> SetRules(ClientServiceParamsInfo param)
{
List<string> errorDevices = new List<string>();
SetRuleParamInfo setRuleParamInfo = param.Content.DeJson<SetRuleParamInfo>();
if (setRuleParamInfo.Devices.Length > 0)
{
byte[] bytes = MemoryPackSerializer.Serialize(setRuleParamInfo.Rules);
for (int i = 0; i < setRuleParamInfo.Devices.Length; i++)
{
if (signCaching.Get(setRuleParamInfo.Devices[i], out SignCacheInfo cache))
{
bool res = await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)HijackMessengerIds.Update,
Payload = bytes
});
if (res == false)
{
errorDevices.Add(setRuleParamInfo.Devices[i]);
}
}
}
}
return errorDevices;
}
public sealed class SetRuleParamInfo
{
public string[] Devices { get; set; }
public SetRuleInfo Rules { get; set; }
}
}
}

View File

@@ -0,0 +1,41 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.llock;
using cmonitor.server.service.messengers.sign;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class LLockClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public LLockClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public async Task<bool> Update(ClientServiceParamsInfo param)
{
LLockUpdateInfo info = param.Content.DeJson<LLockUpdateInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)LLockMessengerIds.Update,
Payload = MemoryPackSerializer.Serialize(info.Value)
});
}
}
return true;
}
}
public sealed class LLockUpdateInfo
{
public string[] Names { get; set; }
public bool Value { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.light;
using cmonitor.server.service.messengers.sign;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class LightClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public LightClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public async Task<bool> Update(ClientServiceParamsInfo param)
{
LightInfo info = param.Content.DeJson<LightInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)LightMessengerIds.Update,
Payload = MemoryPackSerializer.Serialize(info.Value)
});
}
}
return true;
}
}
public sealed class LightInfo
{
public string[] Names { get; set; }
public int Value { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.command;
using cmonitor.server.service.messengers.sign;
using common.libs;
using common.libs.extends;
using System.Diagnostics;
namespace cmonitor.server.api.services
{
public sealed class ReportClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
private readonly IClientServer clientServer;
public ReportClientService(MessengerSender messengerSender, SignCaching signCaching, IClientServer clientServer)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
this.clientServer = clientServer;
}
public bool Update(ClientServiceParamsInfo param)
{
string[] names = param.Content.DeJson<string[]>();
for (int i = 0; i < names.Length; i++)
{
bool res = signCaching.Get(names[i], out SignCacheInfo cache)
&& cache.Connected
&& cache.GetReport()
&& Interlocked.CompareExchange(ref cache.ReportFlag, 0, 1) == 1;
if (res)
{
cache.UpdateReport();
_ = messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ReportMessengerIds.Update,
Timeout = 1000,
}).ContinueWith((result) =>
{
Interlocked.Exchange(ref cache.ReportFlag, 1);
});
}
}
return true;
}
public bool Ping(ClientServiceParamsInfo param)
{
string[] names = param.Content.DeJson<string[]>();
for (int i = 0; i < names.Length; i++)
{
if (signCaching.Get(names[i], out SignCacheInfo cache) && cache.Connected && Interlocked.CompareExchange(ref cache.PingFlag, 0, 1) == 1)
{
string name = names[i];
DateTime starTime = DateTime.Now;
_ = messengerSender.SendReply(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ReportMessengerIds.Ping,
Payload = Helper.EmptyArray,
Timeout = 1000,
}).ContinueWith((result) =>
{
if (result.Result.Code == MessageResponeCodes.OK)
{
clientServer.Notify("/notify/report/pong", new { Name = name, Time = (int)((DateTime.Now - starTime).TotalMilliseconds) }, param.Connection);
}
else
{
clientServer.Notify("/notify/report/pong", new { Name = name, Time = -1 }, param.Connection);
}
Interlocked.Exchange(ref cache.PingFlag, 1);
});
}
}
return true;
}
}
}

View File

@@ -0,0 +1,45 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.screen;
using cmonitor.server.service.messengers.sign;
using common.libs.extends;
namespace cmonitor.server.api.services
{
public sealed class ScreenClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public ScreenClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public bool Update(ClientServiceParamsInfo param)
{
string[] names = param.Content.DeJson<string[]>();
for (int i = 0; i < names.Length; i++)
{
bool res = signCaching.Get(names[i], out SignCacheInfo cache)
&& cache.Connected
&& cache.GetScreen()
&& Interlocked.CompareExchange(ref cache.ScreenFlag, 0, 1) == 1;
if (res)
{
cache.UpdateScreen();
_ = messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)ScreenMessengerIds.Update,
Timeout = 1000,
}).ContinueWith((result) =>
{
Interlocked.Exchange(ref cache.ScreenFlag, 1);
});
}
}
return true;
}
}
}

View File

@@ -0,0 +1,31 @@
using cmonitor.server.service.messengers.sign;
namespace cmonitor.server.api.services
{
public sealed class SignInClientService : IClientService
{
private readonly SignCaching signCaching;
private readonly Config config;
public SignInClientService(SignCaching signCaching, Config config)
{
this.signCaching = signCaching;
this.config = config;
}
public List<SignCacheInfo> List(ClientServiceParamsInfo param)
{
List<SignCacheInfo> caches = signCaching.Get();
return caches;
}
public bool Del(ClientServiceParamsInfo param)
{
signCaching.Del(param.Content);
return true;
}
public Config Config(ClientServiceParamsInfo param)
{
return config;
}
}
}

View File

@@ -0,0 +1,42 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.sign;
using cmonitor.server.service.messengers.usb;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class UsbClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public UsbClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public async Task<bool> Update(ClientServiceParamsInfo param)
{
UsbLockInfo info = param.Content.DeJson<UsbLockInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)UsbMessengerIds.Update,
Payload = MemoryPackSerializer.Serialize(info.Value)
});
}
}
return true;
}
}
public sealed class UsbLockInfo
{
public string[] Names { get; set; }
public bool Value { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.sign;
using cmonitor.server.service.messengers.volume;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class VolumeClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public VolumeClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public async Task<bool> Update(ClientServiceParamsInfo param)
{
VolumeInfo info = param.Content.DeJson<VolumeInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)VolumeMessengerIds.Update,
Payload = MemoryPackSerializer.Serialize(info.Value)
});
}
}
return true;
}
public async Task<bool> Mute(ClientServiceParamsInfo param)
{
VolumeMuteInfo info = param.Content.DeJson<VolumeMuteInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)VolumeMessengerIds.Mute,
Payload = MemoryPackSerializer.Serialize(info.Value)
});
}
}
return true;
}
}
public sealed class VolumeInfo
{
public string[] Names { get; set; }
public float Value { get; set; }
}
public sealed class VolumeMuteInfo
{
public string[] Names { get; set; }
public bool Value { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
using cmonitor.server.service;
using cmonitor.server.service.messengers.sign;
using cmonitor.server.service.messengers.wallpaper;
using common.libs.extends;
using MemoryPack;
namespace cmonitor.server.api.services
{
public sealed class WallpaperClientService : IClientService
{
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
public WallpaperClientService(MessengerSender messengerSender, SignCaching signCaching)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
}
public async Task<bool> Update(ClientServiceParamsInfo param)
{
WallpaperLockInfo info = param.Content.DeJson<WallpaperLockInfo>();
for (int i = 0; i < info.Names.Length; i++)
{
if (signCaching.Get(info.Names[i], out SignCacheInfo cache) && cache.Connected)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache.Connection,
MessengerId = (ushort)WallpaperMessengerIds.Update,
Payload = MemoryPackSerializer.Serialize(new WallpaperUpdateInfo
{
Value = info.Value,
Url = info.Url
})
});
}
}
return true;
}
}
public sealed class WallpaperLockInfo
{
public string[] Names { get; set; }
public bool Value { get; set; }
public string Url { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,487 @@
using common.libs;
using common.libs.extends;
using System.Net;
using System.Net.Sockets;
namespace cmonitor.server.api.websocket
{
/// <summary>
/// wensocket客户端
/// </summary>
public sealed class WebSocketClient : IDisposable
{
private int bufferSize = 4 * 1024;
private SocketAsyncEventArgs readEventArgs;
private bool connected = false;
private bool connecSuccess = false;
/// <summary>
/// 连接中false表示失败会关闭连接
/// </summary>
public Func<WebsocketHeaderInfo, bool> OnConnecting = (header) => { return true; };
/// <summary>
/// 连接失败
/// </summary>
public Action<string> OnConnectFail { get; set; } = (msg) => { };
/// <summary>
/// 已断开连接,未收到关闭帧
/// </summary>
public Action OnDisConnectd = () => { };
/// <summary>
/// 已连接
/// </summary>
public Action<WebsocketHeaderInfo> OnOpen = (header) => { };
/// <summary>
/// 已断开连接,收到关闭帧
/// </summary>
public Action OnClose = () => { };
/// <summary>
/// 文本数据
/// </summary>
public Action<WebSocketFrameInfo, string> OnMessage = (frame, messgae) => { };
/// <summary>
/// 二进制数据
/// </summary>
public Action<WebSocketFrameInfo, Memory<byte>> OnBinary = (frame, data) => { };
/// <summary>
/// 控制帧
/// </summary>
public Action<WebSocketFrameInfo> OnControll = (frame) => { };
/// <summary>
/// 非控制帧
/// </summary>
public Action<WebSocketFrameInfo> OnUnControll = (frame) => { };
/// <summary>
///
/// </summary>
public WebSocketClient()
{
handles = new Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncServerUserToken>> {
//直接添加数据
{ WebSocketFrameInfo.EnumOpcode.Data,HandleAppendData},
//记录opcode并添加
{ WebSocketFrameInfo.EnumOpcode.Text,HandleData},
{ WebSocketFrameInfo.EnumOpcode.Binary,HandleData},
{ WebSocketFrameInfo.EnumOpcode.Close,HandleClose},
{ WebSocketFrameInfo.EnumOpcode.Ping,HandlePing},
{ WebSocketFrameInfo.EnumOpcode.Pong,HandlePong},
};
}
/// <summary>
///
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
public void Connect(IPAddress ip, int port)
{
Connect(new IPEndPoint(ip, port));
}
/// <summary>
///
/// </summary>
/// <param name="ep"></param>
/// <exception cref="Exception"></exception>
public void Connect(IPEndPoint ep)
{
if (connected)
{
throw new Exception("connected");
}
var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.KeepAlive();
AsyncServerUserToken token = new AsyncServerUserToken
{
TargetSocket = socket,
};
SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs
{
UserToken = token,
SocketFlags = SocketFlags.None,
};
connectEventArgs.RemoteEndPoint = ep;
connectEventArgs.Completed += Target_IO_Completed;
if (!socket.ConnectAsync(connectEventArgs))
{
TargetProcessConnect(connectEventArgs);
}
}
private void Target_IO_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
TargetProcessConnect(e);
break;
case SocketAsyncOperation.Receive:
TargetProcessReceive();
break;
default:
break;
}
}
private void TargetProcessConnect(SocketAsyncEventArgs connectEventArgs)
{
AsyncServerUserToken token = (AsyncServerUserToken)connectEventArgs.UserToken;
try
{
if (connectEventArgs.SocketError == SocketError.Success)
{
token.SecWebSocketKey = WebSocketParser.BuildSecWebSocketKey();
byte[] connectData = WebSocketParser.BuildConnectData(new WebsocketHeaderInfo
{
SecWebSocketKey = token.SecWebSocketKey,
});
token.TargetSocket.Send(connectData, SocketFlags.None);
connected = true;
BindTargetReceive(token);
}
else
{
OnConnectFail(connectEventArgs.SocketError.ToString());
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
OnConnectFail(ex.Message);
}
}
private void BindTargetReceive(AsyncServerUserToken connectToken)
{
AsyncServerUserToken token = new AsyncServerUserToken
{
TargetSocket = connectToken.TargetSocket,
SecWebSocketKey = connectToken.SecWebSocketKey,
};
try
{
readEventArgs = new SocketAsyncEventArgs
{
UserToken = token,
SocketFlags = SocketFlags.None,
};
token.PoolBuffer = new byte[bufferSize];
readEventArgs.SetBuffer(token.PoolBuffer, 0, bufferSize);
readEventArgs.Completed += Target_IO_Completed;
if (token.TargetSocket.ReceiveAsync(readEventArgs) == false)
{
TargetProcessReceive();
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
private void TargetProcessReceive()
{
try
{
AsyncServerUserToken token = (AsyncServerUserToken)readEventArgs.UserToken;
if (readEventArgs.BytesTransferred > 0 && readEventArgs.SocketError == SocketError.Success)
{
int offset = readEventArgs.Offset;
int length = readEventArgs.BytesTransferred;
ReadFrame(token, readEventArgs.Buffer.AsMemory(offset, length));
if (token.TargetSocket.Available > 0)
{
while (token.TargetSocket.Available > 0)
{
length = token.TargetSocket.Receive(readEventArgs.Buffer);
if (length > 0)
{
ReadFrame(token, readEventArgs.Buffer.AsMemory(0, length));
}
}
}
if (token.TargetSocket.Connected == false)
{
CloseClientSocket();
return;
}
if (token.TargetSocket.ReceiveAsync(readEventArgs) == false)
{
TargetProcessReceive();
}
}
else
{
CloseClientSocket();
}
}
catch (Exception ex)
{
CloseClientSocket();
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
if (connected)
{
AsyncServerUserToken token = readEventArgs.UserToken as AsyncServerUserToken;
token.Clear();
readEventArgs.Dispose();
OnDisConnectd();
}
connected = false;
connecSuccess = false;
}
private void CloseClientSocket()
{
CloseClientSocket(readEventArgs);
}
/// <summary>
///
/// </summary>
public void Close()
{
CloseClientSocket();
}
private readonly Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncServerUserToken>> handles;
private void ReadFrame(AsyncServerUserToken token, Memory<byte> data)
{
if (connecSuccess)
{
if (token.FrameBuffer.Size == 0 && data.Length > 6)
{
if (WebSocketFrameInfo.TryParse(data, out token.FrameInfo))
{
ExecuteHandle(token);
if (token.FrameInfo.TotalLength == data.Length)
{
return;
}
token.FrameBuffer.AddRange(data.Slice(token.FrameInfo.TotalLength));
}
else
{
token.FrameBuffer.AddRange(data);
}
}
else
{
token.FrameBuffer.AddRange(data);
}
do
{
if (!WebSocketFrameInfo.TryParse(token.FrameBuffer.Data.Slice(token.FrameIndex), out token.FrameInfo))
{
break;
}
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
{
ExecuteHandle(token);
token.FrameBuffer.RemoveRange(0, token.FrameInfo.TotalLength);
token.FrameIndex = 0;
}
else
{
token.FrameBuffer.RemoveRange(token.FrameIndex, token.FrameInfo.TotalLength - token.FrameInfo.PayloadData.Length);
token.FrameIndex += token.FrameInfo.PayloadData.Length;
}
} while (token.FrameBuffer.Size > 6);
}
else
{
HandleConnect(token, data);
}
}
private void ExecuteHandle(AsyncServerUserToken token)
{
if (handles.TryGetValue(token.FrameInfo.Opcode, out Action<AsyncServerUserToken> action))
{
action(token);
}
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll3 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll7)
{
OnUnControll(token.FrameInfo);
}
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll11 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll15)
{
OnControll(token.FrameInfo);
}
else
{
SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Unexpected);
CloseClientSocket();
return;
}
}
private void HandleData(AsyncServerUserToken token)
{
token.Opcode = token.FrameInfo.Opcode;
HandleAppendData(token);
}
private void HandleAppendData(AsyncServerUserToken token)
{
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
{
if (token.Opcode == WebSocketFrameInfo.EnumOpcode.Text)
{
string str = token.FrameInfo.PayloadData.GetString();
OnMessage(token.FrameInfo, str);
}
else
{
OnBinary(token.FrameInfo, token.FrameInfo.PayloadData);
}
}
}
private void HandleClose(AsyncServerUserToken token)
{
SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Normal);
CloseClientSocket();
OnClose();
}
private void HandlePing(AsyncServerUserToken token)
{
SendFramePong();
}
private void HandlePong(AsyncServerUserToken token) { }
private void HandleConnect(AsyncServerUserToken token, Memory<byte> data)
{
WebsocketHeaderInfo header = WebsocketHeaderInfo.Parse(data);
if (!WebSocketParser.VerifySecWebSocketAccept(token.SecWebSocketKey, header.SecWebSocketAccept))
{
OnConnectFail("Sec-WebSocket-Accept Invalid");
CloseClientSocket();
return;
}
if (header.StatusCode != HttpStatusCode.SwitchingProtocols)
{
OnConnectFail($"{(int)header.StatusCode},{header.StatusCode}");
CloseClientSocket();
return;
}
if (!OnConnecting(header))
{
CloseClientSocket();
return;
}
connecSuccess = true;
OnOpen(header);
}
public int SendRaw(byte[] buffer)
{
var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket;
return socket.Send(buffer, SocketFlags.None);
}
public int SendRaw(Memory<byte> buffer)
{
var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket;
return socket.Send(buffer.Span, SocketFlags.None);
}
public int SendFrame(WebSocketFrameRemarkInfo remark)
{
remark.Mask = WebSocketFrameInfo.EnumMask.Mask;
remark.MaskData = WebSocketParser.BuildMaskKey();
var frame = WebSocketParser.BuildFrameData(remark, out int length);
int res = SendRaw(frame.AsMemory(0, length));
WebSocketParser.Return(frame);
return res;
}
public int SendFrameText(string txt)
{
return SendFrameText(txt.ToBytes());
}
public int SendFrameText(byte[] buffer)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Text,
Data = buffer
});
}
public int SendFrameBinary(byte[] buffer)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Binary,
Data = buffer
});
}
public int SendFramePoing()
{
return SendRaw(WebSocketParser.BuildPingData());
}
public int SendFramePong()
{
return SendRaw(WebSocketParser.BuildPongData());
}
public int SendFrameClose(WebSocketFrameInfo.EnumCloseStatus status)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Close,
Data = ((ushort)status).ToBytes()
});
}
public void Dispose()
{
CloseClientSocket();
GC.Collect();
//GC.SuppressFinalize(this);
}
}
public sealed class AsyncServerUserToken
{
public Socket TargetSocket { get; set; }
/// <summary>
/// 当前帧数据
/// </summary>
public WebSocketFrameInfo FrameInfo = null;
/// <summary>
/// 当前帧的数据下标
/// </summary>
public int FrameIndex { get; set; } = 0;
/// <summary>
/// 数据帧缓存
/// </summary>
public ReceiveDataBuffer FrameBuffer { get; } = new ReceiveDataBuffer();
/// <summary>
/// 当前帧的数据类型
/// </summary>
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; }
public Memory<byte> SecWebSocketKey { get; set; }
public byte[] PoolBuffer { get; set; }
public void Clear()
{
TargetSocket?.SafeClose();
TargetSocket = null;
PoolBuffer = Helper.EmptyArray;
//GC.Collect();
//GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,649 @@
using common.libs;
using System.Buffers;
using System.Buffers.Binary;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace cmonitor.server.api.websocket
{
/// <summary>
/// websocket解析器
/// </summary>
public static class WebSocketParser
{
private readonly static SHA1 sha1 = SHA1.Create();
private readonly static Memory<byte> magicCode = Encoding.ASCII.GetBytes("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
/// <summary>
/// 构建连接数据
/// </summary>
/// <param name="header"></param>
/// <returns></returns>
public static byte[] BuildConnectData(WebsocketHeaderInfo header)
{
string path = header.Path.Length == 0 ? "/" : Encoding.UTF8.GetString(header.Path.Span);
StringBuilder sb = new StringBuilder(10);
sb.Append($"GET {path} HTTP/1.1\r\n");
sb.Append($"Upgrade: websocket\r\n");
sb.Append($"Connection: Upgrade\r\n");
sb.Append($"Sec-WebSocket-Version: 13\r\n");
sb.Append($"Sec-WebSocket-Key: {Encoding.UTF8.GetString(header.SecWebSocketKey.Span)}\r\n");
if (header.SecWebSocketProtocol.Length > 0)
{
sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n");
}
if (header.SecWebSocketExtensions.Length > 0)
{
sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n");
}
sb.Append("\r\n");
return Encoding.UTF8.GetBytes(sb.ToString());
}
/// <summary>
/// 构建连接回应数据
/// </summary>
/// <param name="header"></param>
/// <returns></returns>
public static byte[] BuildConnectResponseData(WebsocketHeaderInfo header)
{
string acceptStr = BuildSecWebSocketAccept(header.SecWebSocketKey);
StringBuilder sb = new StringBuilder(10);
sb.Append($"HTTP/1.1 {(int)header.StatusCode} {AddSpace(header.StatusCode)}\r\n");
sb.Append($"Sec-WebSocket-Accept: {acceptStr}\r\n");
if (header.Connection.Length > 0)
{
sb.Append($"Connection: {Encoding.UTF8.GetString(header.Connection.Span)}\r\n");
}
if (header.Upgrade.Length > 0)
{
sb.Append($"Upgrade: {Encoding.UTF8.GetString(header.Upgrade.Span)}\r\n");
}
if (header.SecWebSocketVersion.Length > 0)
{
sb.Append($"Sec-WebSocket-Version: {Encoding.UTF8.GetString(header.SecWebSocketVersion.Span)}\r\n");
}
if (header.SecWebSocketProtocol.Length > 0)
{
sb.Append($"Sec-WebSocket-Protocol: {Encoding.UTF8.GetString(header.SecWebSocketProtocol.Span)}\r\n");
}
if (header.SecWebSocketExtensions.Length > 0)
{
sb.Append($"Sec-WebSocket-Extensions: {Encoding.UTF8.GetString(header.SecWebSocketExtensions.Span)}\r\n");
}
sb.Append("\r\n");
return Encoding.UTF8.GetBytes(sb.ToString());
}
/// <summary>
/// 生成随机key
/// </summary>
/// <returns></returns>
public static byte[] BuildSecWebSocketKey()
{
byte[] bytes = new byte[16];
Random random = new Random(DateTime.Now.Ticks.GetHashCode());
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)random.Next(0, 255);
}
byte[] res = Encoding.UTF8.GetBytes(Convert.ToBase64String(bytes));
return res;
}
/// <summary>
/// 构建mask数据
/// </summary>
/// <returns></returns>
public static byte[] BuildMaskKey()
{
byte[] bytes = new byte[4];
Random random = new Random(DateTime.Now.Ticks.GetHashCode());
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)random.Next(0, 255);
}
return bytes;
}
/// <summary>
/// 生成accept回应
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private static string BuildSecWebSocketAccept(Memory<byte> key)
{
int keyLength = key.Length + magicCode.Length;
byte[] acceptBytes = new byte[keyLength];
key.CopyTo(acceptBytes);
magicCode.CopyTo(acceptBytes.AsMemory(key.Length));
string acceptStr = Convert.ToBase64String(sha1.ComputeHash(acceptBytes, 0, keyLength));
return acceptStr;
}
/// <summary>
/// 验证回应的accept
/// </summary>
/// <param name="key"></param>
/// <param name="accept"></param>
/// <returns></returns>
public static bool VerifySecWebSocketAccept(Memory<byte> key, Memory<byte> accept)
{
string acceptStr = BuildSecWebSocketAccept(key);
return acceptStr == Encoding.UTF8.GetString(accept.Span);
}
/// <summary>
/// 构建ping帧
/// </summary>
/// <returns></returns>
public static byte[] BuildPingData()
{
return new byte[]
{
(byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Ping, //fin + ping
(byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length
};
}
/// <summary>
/// 构建pong帧
/// </summary>
/// <returns></returns>
public static byte[] BuildPongData()
{
return new byte[]
{
(byte)WebSocketFrameInfo.EnumFin.Fin | (byte)WebSocketFrameInfo.EnumOpcode.Pong, //fin + pong
(byte)WebSocketFrameInfo.EnumMask.None | 0, //没有 mask 和 payload length
};
}
/// <summary>
/// 构建数据帧
/// </summary>
/// <param name="remark"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static byte[] BuildFrameData(WebSocketFrameRemarkInfo remark, out int length)
{
if (remark.Mask > 0 && remark.MaskData.Length != 4)
{
throw new Exception("mask data just 4byte");
}
length = 1 + 1 + remark.Data.Length;
int index = 2;
if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask)
{
length += 4;
}
byte payloadLength;
int dataLength = remark.Data.Length;
if (dataLength > ushort.MaxValue)
{
length += 8;
payloadLength = 127;
}
else if (dataLength > 125)
{
length += 2;
payloadLength = 126;
}
else
{
payloadLength = (byte)dataLength;
}
byte[] bytes = ArrayPool<byte>.Shared.Rent(length);
var memory = bytes.AsMemory();
bytes[0] = (byte)((byte)remark.Fin | (byte)remark.Rsv1 | (byte)remark.Rsv2 | (byte)remark.Rsv3 | (byte)remark.Opcode);
bytes[1] = (byte)((byte)remark.Mask | payloadLength);
if (dataLength > ushort.MaxValue)
{
BinaryPrimitives.WriteUInt64BigEndian(memory.Slice(index).Span, (ulong)dataLength);
index += 8;
}
else if (dataLength > 125)
{
BinaryPrimitives.WriteUInt16BigEndian(memory.Slice(index).Span, (ushort)dataLength);
index += 2;
}
if (remark.Mask == WebSocketFrameInfo.EnumMask.Mask)
{
remark.MaskData.CopyTo(bytes.AsMemory(index, remark.MaskData.Length));
index += remark.MaskData.Length;
}
if (remark.Data.Length > 0)
{
remark.Data.CopyTo(bytes.AsMemory(index));
}
return bytes;
}
public static void Return(byte[] bytes)
{
ArrayPool<byte>.Shared.Return(bytes);
}
/// <summary>
/// 给每个大写字母前加一个空格例如ProxyAuthenticationRequired 变成Proxy Authentication Required
/// </summary>
/// <param name="statusCode"></param>
/// <returns></returns>
private static string AddSpace(HttpStatusCode statusCode)
{
ReadOnlySpan<char> span = statusCode.ToString().AsSpan();
int totalLength = span.Length * 2;
char[] result = new char[totalLength];
Span<char> resultSpan = result.AsSpan(0, totalLength);
span.CopyTo(resultSpan);
int length = 0;
for (int i = 0; i < span.Length; i++)
{
if (i > 0 && span[i] >= 65 && span[i] <= 90)
{
resultSpan.Slice(i + length, totalLength - (length + i) - 1).CopyTo(resultSpan.Slice(i + length + 1));
resultSpan[i + length] = (char)32;
length++;
}
}
string resultStr = resultSpan.Slice(0, span.Length + length).ToString();
return resultStr;
}
}
public sealed class WebSocketFrameRemarkInfo
{
/// <summary>
/// 是否是结束帧,如果只有一帧,那必定是结束帧
/// </summary>
public WebSocketFrameInfo.EnumFin Fin { get; set; } = WebSocketFrameInfo.EnumFin.Fin;
/// <summary>
/// 保留位1
/// </summary>
public WebSocketFrameInfo.EnumRsv1 Rsv1 { get; set; } = WebSocketFrameInfo.EnumRsv1.None;
/// <summary>
/// 保留位2
/// </summary>
public WebSocketFrameInfo.EnumRsv2 Rsv2 { get; set; } = WebSocketFrameInfo.EnumRsv2.None;
/// <summary>
/// 保留位3
/// </summary>
public WebSocketFrameInfo.EnumRsv3 Rsv3 { get; set; } = WebSocketFrameInfo.EnumRsv3.None;
/// <summary>
/// 数据描述默认TEXT数据
/// </summary>
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; } = WebSocketFrameInfo.EnumOpcode.Text;
/// <summary>
/// 是否有掩码
/// </summary>
public WebSocketFrameInfo.EnumMask Mask { get; set; } = WebSocketFrameInfo.EnumMask.None;
/// <summary>
/// 掩码key 4字节
/// </summary>
public Memory<byte> MaskData { get; set; }
/// <summary>
/// payload data
/// </summary>
public Memory<byte> Data { get; set; }
}
/// <summary>
/// 数据帧解析
/// </summary>
public sealed class WebSocketFrameInfo
{
/// <summary>
/// 是否是结束帧
/// </summary>
public EnumFin Fin { get; set; }
/// <summary>
/// 保留位
/// </summary>
public EnumRsv1 Rsv1 { get; set; }
/// <summary>
///
/// </summary>
public EnumRsv2 Rsv2 { get; set; }
/// <summary>
///
/// </summary>
public EnumRsv3 Rsv3 { get; set; }
/// <summary>
/// 操作码 0附加数据1文本2二进制3-7为非控制保留8关闭9pinga pongb-f 为控制保留
/// </summary>
public EnumOpcode Opcode { get; set; }
/// <summary>
/// 掩码
/// </summary>
public EnumMask Mask { get; set; }
/// <summary>
/// 总长度
/// </summary>
public int TotalLength { get; set; }
/// <summary>
/// 数据 如果OPCODE是 EnumOpcode.Close 则数据的前2字节为关闭状态码余下的为其它描述数据
/// </summary>
public Memory<byte> PayloadData { get; set; }
/// <summary>
/// 解析帧如果false解析失败则应该把data缓存起来等待下次来数据后拼接起来再次解析
/// </summary>
/// <param name="data"></param>
/// <param name="frameInfo"></param>
/// <returns></returns>
public static bool TryParse(Memory<byte> data, out WebSocketFrameInfo frameInfo)
{
frameInfo = null;
//小于2字节不可解析
if (data.Length < 2)
{
return false;
}
Span<byte> span = data.Span;
int index = 2;
//第2字节
//1位 是否mask
EnumMask mask = (EnumMask)(span[1] & (byte)EnumMask.Mask);
int payloadLength = (span[1] & 0b01111111);
if (payloadLength == 126)
{
payloadLength = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(2, 2));
index += 2;
}
else if (payloadLength == 127)
{
payloadLength = (int)BinaryPrimitives.ReadUInt64BigEndian(span.Slice(2, 8));
index += 8;
}
//数据长+头长 大于 整个数据长,则不是一个完整的包
if (data.Length < payloadLength + index + (mask == EnumMask.Mask ? 4 : 0))
{
return false;
}
//第1字节
//1位 是否是结束帧
EnumFin fin = (EnumFin)(byte)(span[0] & (byte)EnumFin.Fin);
//2 3 4 保留
EnumRsv1 rsv1 = (EnumRsv1)(byte)(span[0] & (byte)EnumRsv1.Rsv);
EnumRsv2 rsv2 = (EnumRsv2)(byte)(span[0] & (byte)EnumRsv2.Rsv);
EnumRsv3 rsv3 = (EnumRsv3)(byte)(span[0] & (byte)EnumRsv3.Rsv);
//5 6 7 8 操作码
EnumOpcode opcode = (EnumOpcode)(byte)(span[0] & (byte)EnumOpcode.Last);
Span<byte> maskKey = Helper.EmptyArray;
if (mask == EnumMask.Mask)
{
//mask掩码key 用来解码数据
maskKey = span.Slice(index, 4);
index += 4;
}
//数据
Memory<byte> payloadData = data.Slice(index, payloadLength);
if (mask == EnumMask.Mask)
{
//解码
Span<byte> payloadDataSpan = payloadData.Span;
for (int i = 0; i < payloadDataSpan.Length; i++)
{
payloadDataSpan[i] = (byte)(payloadDataSpan[i] ^ maskKey[3 & i]);
}
}
frameInfo = new WebSocketFrameInfo
{
Fin = fin,
Rsv1 = rsv1,
Rsv2 = rsv2,
Rsv3 = rsv3,
Opcode = opcode,
Mask = mask,
PayloadData = payloadData,
TotalLength = index + payloadLength
};
return true;
}
public enum EnumFin : byte
{
None = 0x0,
Fin = 0b10000000,
}
public enum EnumMask : byte
{
None = 0x0,
Mask = 0b10000000,
}
public enum EnumRsv1 : byte
{
None = 0x0,
Rsv = 0b01000000,
}
public enum EnumRsv2 : byte
{
None = 0x0,
Rsv = 0b00100000,
}
public enum EnumRsv3 : byte
{
None = 0x0,
Rsv = 0b00010000,
}
public enum EnumOpcode : byte
{
Data = 0x0,
Text = 0x1,
Binary = 0x2,
UnControll3 = 0x3,
UnControll4 = 0x4,
UnControll5 = 0x5,
UnControll6 = 0x6,
UnControll7 = 0x7,
Close = 0x8,
Ping = 0x9,
Pong = 0xa,
Controll11 = 0xb,
Controll12 = 0xc,
Controll13 = 0xd,
Controll14 = 0xe,
Controll15 = 0xf,
Last = 0xf,
}
/// <summary>
/// 关闭的状态码
/// </summary>
public enum EnumCloseStatus : ushort
{
/// <summary>
/// 正常关闭
/// </summary>
Normal = 1000,
/// <summary>
/// 正在离开
/// </summary>
Leaving = 1001,
/// <summary>
/// 协议错误
/// </summary>
ProtocolError = 1002,
/// <summary>
/// 只能接收TEXT数据
/// </summary>
TextOnly = 1003,
/// <summary>
/// 保留
/// </summary>
None1004 = 1004,
/// <summary>
/// 保留
/// </summary>
None1005 = 1005,
/// <summary>
/// 保留
/// </summary>
None1006 = 1006,
/// <summary>
/// 消息类型不一致
/// </summary>
DataTypeError = 1007,
/// <summary>
/// 通用状态码,没有别的合适的状态码时,用这个
/// </summary>
PublicError = 1008,
/// <summary>
/// 数据太大,无法处理
/// </summary>
DataTooBig = 1009,
/// <summary>
/// 扩展错误
/// </summary>
ExtendsError = 1010,//正常关闭
/// <summary>
/// 意外情况
/// </summary>
Unexpected = 1011,
/// <summary>
/// TLS握手失败
/// </summary>
TLSError = 1015
}
}
/// <summary>
/// 请求头解析
/// </summary>
public sealed class WebsocketHeaderInfo
{
static byte[][] bytes = new byte[][] {
Encoding.ASCII.GetBytes("Connection: "),
Encoding.ASCII.GetBytes("Upgrade: "),
Encoding.ASCII.GetBytes("Origin: "),
Encoding.ASCII.GetBytes("Sec-WebSocket-Version: "),
Encoding.ASCII.GetBytes("Sec-WebSocket-Key: "),
Encoding.ASCII.GetBytes("Sec-WebSocket-Extensions: "),
Encoding.ASCII.GetBytes("Sec-WebSocket-Protocol: "),
Encoding.ASCII.GetBytes("Sec-WebSocket-Accept: "),
};
static byte[] httpBytes = Encoding.UTF8.GetBytes("HTTP/");
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.SwitchingProtocols;
public Memory<byte> Method { get; private set; }
private string _pathSet { get; set; }
/// <summary>
/// 用这个设置path值
/// </summary>
public string PathSet
{
get
{
return _pathSet;
}
set
{
_pathSet = value;
Path = Encoding.UTF8.GetBytes(_pathSet);
}
}
/// <summary>
/// 如果 仅1个字符那就是 /
/// </summary>
public Memory<byte> Path { get; private set; }
public Memory<byte> Connection { get; private set; }
public Memory<byte> Upgrade { get; private set; }
public Memory<byte> Origin { get; private set; }
public Memory<byte> SecWebSocketVersion { get; private set; }
public Memory<byte> SecWebSocketKey { get; set; }
public Memory<byte> SecWebSocketExtensions { get; set; }
public Memory<byte> SecWebSocketProtocol { get; set; }
public Memory<byte> SecWebSocketAccept { get; set; }
public static WebsocketHeaderInfo Parse(Memory<byte> header)
{
Span<byte> span = header.Span;
int flag = 0xff;
int bit = 0x01;
ulong[] res = new ulong[bytes.Length];
for (int i = 0, len = span.Length; i < len; i++)
{
if (span[i] == 13 && span[i + 1] == 10 && span[i + 2] == 13 && span[i + 3] == 10)
{
break;
}
if (span[i] == 13 && span[i + 1] == 10)
{
int startIndex = i + 2;
for (int k = 0; k < bytes.Length; k++)
{
if ((flag >> k & 1) == 1 && span[startIndex] == bytes[k][0])
{
if (span.Slice(startIndex, bytes[k].Length).SequenceEqual(bytes[k]))
{
int index = span.Slice(startIndex).IndexOf((byte)13);
flag &= ~(bit << k);
#pragma warning disable CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符
res[k] = ((ulong)(startIndex + bytes[k].Length) << 32) | (ulong)(index - bytes[k].Length);
#pragma warning restore CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符
i += index + 1;
break;
}
}
}
}
}
WebsocketHeaderInfo headerInfo = new WebsocketHeaderInfo
{
Connection = header.Slice((int)(res[0] >> 32), (int)(res[0] & 0xffffffff)),
Upgrade = header.Slice((int)(res[1] >> 32), (int)(res[1] & 0xffffffff)),
Origin = header.Slice((int)(res[2] >> 32), (int)(res[2] & 0xffffffff)),
SecWebSocketVersion = header.Slice((int)(res[3] >> 32), (int)(res[3] & 0xffffffff)),
SecWebSocketKey = header.Slice((int)(res[4] >> 32), (int)(res[4] & 0xffffffff)),
SecWebSocketExtensions = header.Slice((int)(res[5] >> 32), (int)(res[5] & 0xffffffff)),
SecWebSocketProtocol = header.Slice((int)(res[6] >> 32), (int)(res[6] & 0xffffffff)),
SecWebSocketAccept = header.Slice((int)(res[7] >> 32), (int)(res[7] & 0xffffffff)),
};
int pathIndex = span.IndexOf((byte)32);
int pathIndex1 = span.Slice(pathIndex + 1).IndexOf((byte)32);
if (header.Slice(0, httpBytes.Length).Span.SequenceEqual(httpBytes))
{
int code = int.Parse(Encoding.UTF8.GetString(header.Slice(pathIndex + 1, pathIndex1).Span));
headerInfo.StatusCode = (HttpStatusCode)code;
}
else
{
headerInfo.Path = header.Slice(pathIndex + 1, pathIndex1);
headerInfo.Method = header.Slice(0, pathIndex);
}
return headerInfo;
}
}
}

View File

@@ -0,0 +1,476 @@
using common.libs;
using common.libs.extends;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace cmonitor.server.api.websocket
{
/// <summary>
/// websocket服务端
/// </summary>
public sealed class WebSocketServer
{
private Socket socket;
private int BufferSize = 4 * 1024;
private readonly ConcurrentDictionary<ulong, AsyncUserToken> connections = new();
private readonly NumberSpaceUInt32 numberSpace = new NumberSpaceUInt32(0);
/// <summary>
/// 收到连接,可以在这处理 subProtocol extensions 及其它信息false表示阻止连接应设置header 的 StatusCode
/// </summary>
public Func<WebsocketConnection, WebsocketHeaderInfo, bool> OnConnecting = (connection, header) =>
{
header.SecWebSocketExtensions = Helper.EmptyArray; return true;
};
/// <summary>
/// 已断开连接,没有收到关闭帧
/// </summary>
public Action<WebsocketConnection> OnDisConnectd = (connection) => { };
/// <summary>
/// 已连接
/// </summary>
public Action<WebsocketConnection> OnOpen = (connection) => { };
/// <summary>
/// 已关闭,收到关闭帧
/// </summary>
public Action<WebsocketConnection> OnClose = (connection) => { };
/// <summary>
/// 文本数据
/// </summary>
public Action<WebsocketConnection, WebSocketFrameInfo, string> OnMessage = (connection, frame, message) => { };
/// <summary>
/// 二进制数据
/// </summary>
public Action<WebsocketConnection, WebSocketFrameInfo, Memory<byte>> OnBinary = (connection, frame, data) => { };
/// <summary>
/// 控制帧,保留的控制帧,可以自定义处理
/// </summary>
public Action<WebsocketConnection, WebSocketFrameInfo> OnControll = (connection, frame) => { };
/// <summary>
/// 非控制帧,保留的非控制帧,可以自定义处理
/// </summary>
public Action<WebsocketConnection, WebSocketFrameInfo> OnUnControll = (connection, frame) => { };
/// <summary>
///
/// </summary>
public IEnumerable<WebsocketConnection> Connections
{
get
{
return connections.Values.Select(c => c.Connectrion);
}
}
public WebSocketServer()
{
handles = new Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncUserToken>> {
//直接添加数据
{ WebSocketFrameInfo.EnumOpcode.Data,HandleAppendData},
//记录opcode并添加
{ WebSocketFrameInfo.EnumOpcode.Text,HandleData},
{ WebSocketFrameInfo.EnumOpcode.Binary,HandleData},
{ WebSocketFrameInfo.EnumOpcode.Close,HandleClose},
{ WebSocketFrameInfo.EnumOpcode.Ping,HandlePing},
{ WebSocketFrameInfo.EnumOpcode.Pong,HandlePong},
};
}
public void Start(IPAddress bindip, int port)
{
IPEndPoint localEndPoint = new IPEndPoint(bindip, port);
socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.Bind(localEndPoint);
socket.Listen(int.MaxValue);
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs
{
UserToken = socket,
SocketFlags = SocketFlags.None,
};
acceptEventArg.Completed += IO_Completed;
StartAccept(acceptEventArg);
}
private void IO_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Accept:
ProcessAccept(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
break;
}
}
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
acceptEventArg.AcceptSocket = null;
Socket listenSocket = ((Socket)acceptEventArg.UserToken);
try
{
if (!listenSocket.AcceptAsync(acceptEventArg))
{
ProcessAccept(acceptEventArg);
}
}
catch (Exception)
{
}
}
private void ProcessAccept(SocketAsyncEventArgs e)
{
BindReceive(e.AcceptSocket);
StartAccept(e);
}
private void BindReceive(Socket socket)
{
socket.KeepAlive(10,5);
AsyncUserToken token = new AsyncUserToken
{
Connectrion = new WebsocketConnection { Socket = socket, Id = numberSpace.Increment() }
};
connections.TryAdd(token.Connectrion.Id, token);
SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs
{
UserToken = token,
SocketFlags = SocketFlags.None,
};
token.PoolBuffer = new byte[BufferSize];
readEventArgs.SetBuffer(token.PoolBuffer, 0, BufferSize);
readEventArgs.Completed += IO_Completed;
if (socket.ReceiveAsync(readEventArgs) == false)
{
ProcessReceive(readEventArgs);
}
}
private void ProcessReceive(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
try
{
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
var memory = e.Buffer.AsMemory(e.Offset, e.BytesTransferred);
ReadFrame(token, memory);
if (token.Connectrion.Socket.Available > 0)
{
while (token.Connectrion.Socket.Available > 0)
{
int length = token.Connectrion.Socket.Receive(e.Buffer);
if (length > 0)
{
memory = e.Buffer.AsMemory(0, length);
ReadFrame(token, memory);
}
}
}
if (!token.Connectrion.Socket.Connected)
{
CloseClientSocket(e);
return;
}
if (!token.Connectrion.Socket.ReceiveAsync(e))
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
catch (Exception)
{
CloseClientSocket(e);
}
}
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (!token.Connectrion.Socket.ReceiveAsync(e))
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
if (token.Disposabled == false)
{
e.Dispose();
if (connections.TryRemove(token.Connectrion.Id, out _))
{
token.Clear();
}
OnDisConnectd(token.Connectrion);
}
}
public void Stop()
{
socket?.SafeClose();
foreach (var item in connections.Values)
{
item.Clear();
}
connections.Clear();
}
private readonly Dictionary<WebSocketFrameInfo.EnumOpcode, Action<AsyncUserToken>> handles;
/// <summary>
/// 读取数据帧分帧、粘包、半包处理得到完整的包再根据opcode交给对应的处理
/// </summary>
/// <param name="token"></param>
/// <param name="data"></param>
private void ReadFrame(AsyncUserToken token, Memory<byte> data)
{
if (token.Connectrion.Connected)
{
if (token.FrameBuffer.Size == 0 && data.Length > 6)
{
if (WebSocketFrameInfo.TryParse(data, out token.FrameInfo))
{
ExecuteHandle(token);
if (token.FrameInfo.TotalLength == data.Length)
{
return;
}
token.FrameBuffer.AddRange(data.Slice(token.FrameInfo.TotalLength));
}
else
{
token.FrameBuffer.AddRange(data);
}
}
else
{
token.FrameBuffer.AddRange(data);
}
do
{
if (WebSocketFrameInfo.TryParse(token.FrameBuffer.Data.Slice(token.FrameIndex), out token.FrameInfo) == false)
{
break;
}
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
{
ExecuteHandle(token);
token.FrameBuffer.RemoveRange(0, token.FrameInfo.TotalLength);
token.FrameIndex = 0;
}
else
{
token.FrameBuffer.RemoveRange(token.FrameIndex, token.FrameInfo.TotalLength - token.FrameInfo.PayloadData.Length);
token.FrameIndex += token.FrameInfo.PayloadData.Length;
}
} while (token.FrameBuffer.Size > 6);
}
else
{
HandleConnect(token, data);
}
}
private void ExecuteHandle(AsyncUserToken token)
{
if (handles.TryGetValue(token.FrameInfo.Opcode, out Action<AsyncUserToken> action))
{
action(token);
}
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll3 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll7)
{
OnUnControll(token.Connectrion, token.FrameInfo);
}
else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll11 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll15)
{
OnControll(token.Connectrion, token.FrameInfo);
}
else
{
token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.ExtendsError);
token.Connectrion.Close();
return;
}
}
private void HandleData(AsyncUserToken token)
{
token.Opcode = token.FrameInfo.Opcode;
HandleAppendData(token);
}
private void HandleAppendData(AsyncUserToken token)
{
if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin)
{
if (token.Opcode == WebSocketFrameInfo.EnumOpcode.Text)
{
string str = token.FrameInfo.PayloadData.GetString();
OnMessage(token.Connectrion, token.FrameInfo, str);
}
else
{
OnBinary(token.Connectrion, token.FrameInfo, token.FrameInfo.PayloadData);
}
}
}
private void HandleClose(AsyncUserToken token)
{
token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Normal);
token.Connectrion.Close();
OnClose(token.Connectrion);
}
private void HandlePing(AsyncUserToken token)
{
token.Connectrion.SendFramePong();
}
private void HandlePong(AsyncUserToken token) { }
private void HandleConnect(AsyncUserToken token, Memory<byte> data)
{
WebsocketHeaderInfo header = WebsocketHeaderInfo.Parse(data);
if (header.SecWebSocketKey.Length == 0)
{
header.StatusCode = HttpStatusCode.MethodNotAllowed;
token.Connectrion.ConnectResponse(header);
token.Connectrion.Close();
return;
}
if (OnConnecting(token.Connectrion, header))
{
token.Connectrion.Connected = true;
token.Connectrion.ConnectResponse(header);
OnOpen(token.Connectrion);
}
else
{
token.Connectrion.ConnectResponse(header);
token.Connectrion.Close();
}
}
}
public sealed class WebsocketConnection
{
public uint Id { get; set; }
public Socket Socket { get; init; }
public bool Connected { get; set; } = false;
public bool SocketConnected=> Socket != null && Socket.Connected;
private bool Closed = false;
public int ConnectResponse(WebsocketHeaderInfo header)
{
var data = WebSocketParser.BuildConnectResponseData(header);
return SendRaw(data);
}
public int SendRaw(byte[] buffer)
{
return Socket.Send(buffer, SocketFlags.None);
}
public int SendRaw(Memory<byte> buffer)
{
return Socket.Send(buffer.Span, SocketFlags.None);
}
public int SendFrame(WebSocketFrameRemarkInfo remark)
{
var frame = WebSocketParser.BuildFrameData(remark, out int length);
int res = SendRaw(frame.AsMemory(0, length));
WebSocketParser.Return(frame);
return res;
}
public int SendFrameText(string txt)
{
return SendFrameText(txt.ToBytes());
}
public int SendFrameText(byte[] buffer)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Text,
Data = buffer
});
}
public int SendFrameBinary(Memory<byte> buffer)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Binary,
Data = buffer
});
}
public int SendFramePong()
{
return SendRaw(WebSocketParser.BuildPongData());
}
public int SendFrameClose(WebSocketFrameInfo.EnumCloseStatus status)
{
return SendFrame(new WebSocketFrameRemarkInfo
{
Opcode = WebSocketFrameInfo.EnumOpcode.Close,
Data = ((ushort)status).ToBytes()
});
}
public void Close()
{
if (!Closed)
{
Socket?.SafeClose();
}
Closed = true;
Connected = false;
}
}
public sealed class AsyncUserToken
{
public WebsocketConnection Connectrion { get; set; }
/// <summary>
/// 当前帧数据
/// </summary>
public WebSocketFrameInfo FrameInfo = null;
/// <summary>
/// 当前帧的数据下标
/// </summary>
public int FrameIndex { get; set; } = 0;
/// <summary>
/// 数据帧缓存
/// </summary>
public ReceiveDataBuffer FrameBuffer { get; } = new ReceiveDataBuffer();
/// <summary>
/// 当前帧的数据类型
/// </summary>
public WebSocketFrameInfo.EnumOpcode Opcode { get; set; }
public byte[] PoolBuffer { get; set; }
public bool Disposabled { get; private set; } = false;
public void Clear()
{
Disposabled = true;
Connectrion.Close();
}
}
}

View File

@@ -0,0 +1,35 @@
using common.libs.database;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace cmonitor.server.client
{
[Table("client")]
public sealed class ClientConfig
{
private readonly IConfigDataProvider<ClientConfig> configDataProvider;
public ClientConfig() { }
public ClientConfig(IConfigDataProvider<ClientConfig> configDataProvider)
{
this.configDataProvider = configDataProvider;
ClientConfig config = configDataProvider.Load().Result ?? new ClientConfig();
Lock = config.Lock;
Wallpaper = config.Wallpaper;
Usb = config.Usb;
Save();
}
public bool Lock { get; set; }
public bool Wallpaper { get; set; }
public bool Usb { get; set; }
public void Save()
{
configDataProvider.Save(this).Wait();
}
}
}

View File

@@ -0,0 +1,12 @@
using cmonitor.server.service;
namespace cmonitor.server.client
{
public sealed class ClientSignInState
{
public IConnection Connection { get; set; }
public bool Connected => Connection != null && Connection.Connected;
}
}

View File

@@ -0,0 +1,110 @@
using cmonitor.server.client.reports;
using cmonitor.server.service;
using cmonitor.server.service.messengers.report;
using cmonitor.server.service.messengers.sign;
using common.libs;
using common.libs.extends;
using MemoryPack;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace cmonitor.server.client
{
public sealed class ClientTransfer
{
private readonly ClientSignInState clientSignInState;
private readonly Config config;
private readonly TcpServer tcpServer;
private readonly MessengerSender messengerSender;
public ClientTransfer(ClientSignInState clientSignInState, Config config, TcpServer tcpServer, MessengerSender messengerSender)
{
this.clientSignInState = clientSignInState;
this.config = config;
this.tcpServer = tcpServer;
this.messengerSender = messengerSender;
if (config.IsCLient)
{
SignInTask();
}
}
private void SignInTask()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
if (clientSignInState.Connected == false)
{
try
{
await SignIn();
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
await Task.Delay(1000);
}
}, TaskCreationOptions.LongRunning);
}
private async Task SignIn()
{
IPAddress[] ips = new IPAddress[] { config.BroadcastIP };
//if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Info($"get ip:{ips.ToJson()}");
if (ips.Length == 0) return;
foreach (IPAddress ip in ips)
{
try
{
IPEndPoint remote = new IPEndPoint(ip, config.ServicePort);
Socket socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.KeepAlive();
IAsyncResult result = socket.BeginConnect(remote, null, null);
await Task.Delay(500);
if (result.IsCompleted == false)
{
socket.SafeClose();
continue;
}
clientSignInState.Connection = tcpServer.BindReceive(socket);
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)SignInMessengerIds.SignIn,
Payload = MemoryPackSerializer.Serialize(new SignInfo
{
MachineName = config.Name,
Version = config.Version
})
});
if (resp.Code != MessageResponeCodes.OK || resp.Data.Span.SequenceEqual(Helper.TrueArray) == false)
{
clientSignInState.Connection?.Disponse();
continue;
}
GCHelper.FlushMemory();
break;
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
using cmonitor.server.service.messengers.sign;
using cmonitor.server.service;
using common.libs;
using MemoryPack;
using Microsoft.Extensions.DependencyInjection;
using common.libs.extends;
using System.Reflection;
namespace cmonitor.server.client.reports
{
public interface IReport
{
public string Name { get; }
public Dictionary<string, object> GetReports();
}
public sealed class ReportTransfer : IReport
{
public string Name => string.Empty;
private readonly ClientSignInState clientSignInState;
private readonly MessengerSender messengerSender;
private readonly ServiceProvider serviceProvider;
private readonly Config config;
private List<IReport> reports;
private Dictionary<string, Dictionary<string, object>> reportObj;
public ReportTransfer(ClientSignInState clientSignInState, MessengerSender messengerSender, ServiceProvider serviceProvider, Config config)
{
this.clientSignInState = clientSignInState;
this.messengerSender = messengerSender;
this.serviceProvider = serviceProvider;
this.config = config;
if (config.IsCLient)
{
ReportTask();
}
}
public Dictionary<string, object> GetReports()
{
return new Dictionary<string, object>();
}
public void LoadPlugins(Assembly[] assembs)
{
IEnumerable<Type> types = ReflectionHelper.GetInterfaceSchieves(assembs, typeof(IReport));
reports = types.Select(c => (IReport)serviceProvider.GetService(c)).Where(c => string.IsNullOrWhiteSpace(c.Name) == false).ToList();
reportObj = new Dictionary<string, Dictionary<string, object>>(reports.Count);
}
private uint reportFlag = 0;
public void Update()
{
Interlocked.CompareExchange(ref reportFlag, 1, 0);
}
private void ReportTask()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
try
{
if (clientSignInState.Connected == true && Interlocked.CompareExchange(ref reportFlag, 0, 1) == 1)
{
await SendReport();
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
await Task.Delay(Config.ReportTime);
}
}, TaskCreationOptions.LongRunning);
}
private async Task SendReport()
{
reportObj.Clear();
foreach (IReport item in reports)
{
if (string.IsNullOrWhiteSpace(item.Name) == false)
{
reportObj.Add(item.Name, item.GetReports());
}
}
byte[] res = MemoryPackSerializer.Serialize(reportObj.ToJson());
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)ReportMessengerIds.Report,
Payload = res
});
}
}
}

View File

@@ -0,0 +1,360 @@
using common.libs;
using MemoryPack;
#if DEBUG || RELEASE
using Microsoft.Win32;
#endif
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace cmonitor.server.client.reports.active
{
public sealed class ActiveWindowReport : IReport
{
public string Name => "ActiveWindow";
private readonly ActiveWindowTimeManager activeWindowTimeManager = new ActiveWindowTimeManager();
private ActiveWindow active = new ActiveWindow();
private Dictionary<string, object> report = new Dictionary<string, object> {
{ "Title", string.Empty }, { "FileName", string.Empty }, { "Desc", string.Empty }, { "Pid", 0 }, { "Disallow", Array.Empty<string>() }
};
private string[] disallowList = Array.Empty<string>();
private List<string> disallowTitles = new List<string>();
private ActiveWindow[] backgroundWindow = Array.Empty<ActiveWindow>();
private readonly Config config;
public ActiveWindowReport(Config config)
{
this.config = config;
if (config.IsCLient)
{
Timers();
}
DisallowInit();
}
public Dictionary<string, object> GetReports()
{
//GetActiveWindow();
report["Title"] = active.Title;
report["FileName"] = active.FileName;
report["Desc"] = active.Desc;
report["Pid"] = active.Pid;
report["Count"] = disallowList.Length;
report["Backs"] = backgroundWindow;
return report;
}
public ActiveWindowTimeReportInfo GetActiveWindowTimes()
{
return activeWindowTimeManager.GetActiveWindowTimes();
}
public void ClearActiveWindowTimes()
{
activeWindowTimeManager.Clear();
}
public void DisallowRun(string[] names)
{
DisallowRun(false);
DisallowRunClear();
disallowList = names;
if (names.Length > 0)
{
DisallowRun(true);
DisallowRunFileNames(names);
disallowTitles = names.Where(c => c.EndsWith(".exe") == false).ToList();
}
Task.Run(() =>
{
CommandHelper.Windows(string.Empty, new string[] { "gpupdate /force" });
});
}
private void Timers()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
try
{
GetActiveWindow();
if (disallowTitles.Count > 0 && disallowTitles.Contains(active.Title))
{
uint pid = active.Pid;
_ = Task.Run(() =>
{
CommandHelper.Windows(string.Empty, new string[] { $"taskkill /f /pid {pid}" });
});
}
else
{
activeWindowTimeManager.Update(active);
}
WindowHelper.UpdateCurrentProcesses();
if (WindowHelper.processes != null)
{
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
await Task.Delay(500);
}
}, TaskCreationOptions.LongRunning);
}
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("psapi.dll")]
static extern int GetProcessImageFileName(IntPtr hProcess, StringBuilder lpImageFileName, int nSize);
const int nChars = 256;
static StringBuilder buff = new StringBuilder(nChars);
private void GetActiveWindow()
{
IntPtr handle = GetForegroundWindow();
GetWindowThreadProcessId(handle, out uint id);
if (GetWindowText(handle, buff, nChars) > 0)
{
Process p = Process.GetProcessById((int)id);
string desc = string.Empty;
string filename = string.Empty;
try
{
ProcessModule main = p.MainModule;
if (main != null)
{
filename = main.FileName;
desc = main.FileVersionInfo.FileDescription;
}
}
catch (Exception)
{
}
active.Title = buff.ToString();
active.FileName = filename;
active.Desc = desc;
active.Pid = id;
return;
}
active.Title = string.Empty;
active.FileName = string.Empty;
active.Desc = string.Empty;
active.Pid = 0;
}
private void DisallowInit()
{
CreateKey();
DisallowRunClear();
DisallowRun(false);
Task.Run(() =>
{
CommandHelper.Windows(string.Empty, new string[] { "gpupdate /force" });
});
}
private void DisallowRunClear()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\DisallowRun", true);
if (key != null)
{
string[] names = key.GetValueNames();
if (names != null)
{
foreach (string name in names)
{
key.DeleteValue(name, false);
}
}
}
}
}
catch (Exception ex)
{
Logger.Instance.Error($"application disallow clear {ex}");
}
#endif
}
private void DisallowRunFileNames(string[] filenames)
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\DisallowRun", true);
if (key != null)
{
foreach (string filename in filenames)
{
key.SetValue(filename, filename, RegistryValueKind.String);
}
}
}
}
catch (Exception ex)
{
Logger.Instance.Error($"application disallow {string.Join(",", filenames)} {ex}");
}
#endif
}
private void DisallowRun(bool value)
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", true);
if (key != null)
{
key.SetValue("DisallowRun", value ? 1 : 0, RegistryValueKind.DWord);
}
}
}
catch (Exception)
{
}
#endif
}
private void CreateKey()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", true);
RegistryKey disallowRun = key.OpenSubKey("DisallowRun");
if (disallowRun == null)
{
key.CreateSubKey("DisallowRun");
}
}
}
catch (Exception)
{
}
#endif
}
}
public sealed class ActiveWindow
{
public string Title { get; set; } = string.Empty;
public string FileName { get; set; } = string.Empty;
public string Desc { get; set; } = string.Empty;
public uint Pid = 0;
}
public sealed class ActiveWindowTimeManager
{
private ConcurrentDictionary<string, ActiveWindowTimeInfo> dic = new ConcurrentDictionary<string, ActiveWindowTimeInfo>();
private string lastFileName = string.Empty;
private string lastTitle = string.Empty;
private DateTime StartTime = DateTime.Now;
public void Clear()
{
StartTime = DateTime.Now;
dic.Clear();
GC.Collect();
}
public ActiveWindowTimeReportInfo GetActiveWindowTimes()
{
return new ActiveWindowTimeReportInfo
{
StartTime = StartTime,
List = dic.Values.ToList()
};
}
public void Update(ActiveWindow active)
{
if (string.IsNullOrWhiteSpace(active.FileName)) return;
int index = active.FileName.LastIndexOf('\\');
string filename = active.FileName;
if (index >= 0)
{
filename = filename.Substring(index + 1, filename.Length - index - 1);
}
if (dic.TryGetValue(filename, out ActiveWindowTimeInfo info) == false)
{
info = new ActiveWindowTimeInfo
{
FileName = filename,
Desc = active.Desc,
StartTime = DateTime.Now,
Titles = new Dictionary<string, uint>()
};
dic.TryAdd(filename, info);
}
if (string.IsNullOrWhiteSpace(lastFileName) == false)
{
if (dic.TryGetValue(lastFileName, out ActiveWindowTimeInfo lastInfo))
{
lastInfo.Time += (ulong)(DateTime.Now - lastInfo.StartTime).TotalMilliseconds;
if (string.IsNullOrWhiteSpace(lastTitle) == false)
{
if (info.Titles.TryGetValue(lastTitle, out uint times) == false)
{
info.Titles.TryAdd(lastTitle, 0);
}
info.Titles[lastTitle] += (uint)(DateTime.Now - lastInfo.StartTime).TotalMilliseconds;
}
}
}
info.StartTime = DateTime.Now;
lastFileName = filename;
lastTitle = active.Title;
}
}
[MemoryPackable]
public sealed partial class ActiveWindowTimeReportInfo
{
public DateTime StartTime { get; set; } = DateTime.Now;
public List<ActiveWindowTimeInfo> List { get; set; } = new List<ActiveWindowTimeInfo>();
}
[MemoryPackable]
public sealed partial class ActiveWindowTimeInfo
{
public string FileName { get; set; }
public string Desc { get; set; }
public ulong Time { get; set; }
public DateTime StartTime { get; set; }
public Dictionary<string, uint> Titles { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
using cmonitor.hijack;
using common.libs;
namespace cmonitor.server.client.reports.hijack
{
internal sealed class HijackReport : IReport
{
public string Name => "Hijack";
private readonly HijackEventHandler hijackEventHandler;
private readonly HijackConfig hijackConfig;
public HijackReport(HijackEventHandler hijackEventHandler, HijackController hijackController, HijackConfig hijackConfig)
{
this.hijackEventHandler = hijackEventHandler;
this.hijackConfig = hijackConfig;
if (OperatingSystem.IsWindows())
{
try
{
hijackController.Start();
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}
}
public Dictionary<string, object> GetReports()
{
ulong upload = hijackEventHandler.UdpSend + hijackEventHandler.TcpSend;
ulong download = hijackEventHandler.TcpReceive + hijackEventHandler.UdpReceive;
return new Dictionary<string, object> {
{ "Upload",upload},
{ "Download",download},
{ "Count",hijackConfig.AllowIPs.Length + hijackConfig.DeniedIPs.Length + hijackConfig.AllowDomains.Length+hijackConfig.DeniedDomains.Length + hijackConfig.AllowProcesss.Length+hijackConfig.DeniedProcesss.Length }
};
}
}
}

View File

@@ -0,0 +1,33 @@
namespace cmonitor.server.client.reports.light
{
public sealed class LightReport : IReport
{
public string Name => "Light";
private readonly LightWatcher lightWatcher;
public LightReport()
{
if (OperatingSystem.IsWindows())
{
lightWatcher = new LightWatcher();
lightWatcher.BrightnessChanged += (e, value) =>
{
dic["Value"] = value.newBrightness;
};
dic["Value"] = LightWmiHelper.GetBrightnessLevel();
}
}
Dictionary<string, object> dic = new Dictionary<string, object> { { "Value", 0 } };
public Dictionary<string, object> GetReports()
{
return dic;
}
public void SetLight(int value)
{
LightWmiHelper.SetBrightnessLevel(value);
}
}
}

View File

@@ -0,0 +1,71 @@
#if DEBUG || RELEASE
using System.Management;
#endif
namespace cmonitor.server.client.reports.light
{
public class LightWatcher : IDisposable
{
public event EventHandler<BrightnessChangedEventArgs> BrightnessChanged;
public class BrightnessChangedEventArgs : EventArgs
{
public object newBrightness { get; set; } // new screen brightness value
public BrightnessChangedEventArgs(object b)
{
this.newBrightness = b;
}
}
#if DEBUG || RELEASE
private void WmiEventHandler(object sender, EventArrivedEventArgs e)
{
if (OperatingSystem.IsWindows())
{
if (BrightnessChanged != null)
{
BrightnessChanged(this, new BrightnessChangedEventArgs(e.NewEvent.Properties["Brightness"].Value));
}
}
}
private readonly ManagementEventWatcher _watcher;
#endif
public LightWatcher()
{
#if DEBUG || RELEASE
if (OperatingSystem.IsWindows())
{
try
{
var scope = @"root\wmi";
var query = "SELECT * FROM WmiMonitorBrightnessEvent";
_watcher = new ManagementEventWatcher(scope, query);
_watcher.EventArrived += new EventArrivedEventHandler(WmiEventHandler);
_watcher.Start();
}
catch (Exception e)
{
Console.WriteLine("Exception {0} Trace {1}", e.Message, e.StackTrace);
}
}
#endif
}
public void Dispose()
{
#if DEBUG || RELEASE
if (OperatingSystem.IsWindows())
{
if (_watcher != null)
{
_watcher.Stop();
}
_watcher.Dispose();
}
#endif
}
}
}

View File

@@ -0,0 +1,91 @@
#if DEBUG || RELEASE
using System.Management;
#endif
using common.libs;
namespace cmonitor.server.client.reports.light
{
public static class LightWmiHelper
{
public static int GetBrightnessLevel()
{
#if DEBUG || RELEASE
if (OperatingSystem.IsWindows())
{
try
{
var s = new ManagementScope("root\\WMI");
var q = new SelectQuery("WmiMonitorBrightness");
var mos = new ManagementObjectSearcher(s, q);
var moc = mos.Get();
foreach (var managementBaseObject in moc)
{
foreach (var o in managementBaseObject.Properties)
{
if (o.Name == "CurrentBrightness")
return Convert.ToInt32(o.Value);
}
}
moc.Dispose();
mos.Dispose();
}
catch (ManagementException)
{
// ignore
// it is possible that laptop lid is closed, and using external monitor
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
#endif
return 0;
}
public static void SetBrightnessLevel(int brightnessLevel)
{
#if DEBUG || RELEASE
if (OperatingSystem.IsWindows())
{
if (brightnessLevel < 0 ||
brightnessLevel > 100)
throw new ArgumentOutOfRangeException("brightnessLevel");
try
{
var s = new ManagementScope("root\\WMI");
var q = new SelectQuery("WmiMonitorBrightnessMethods");
var mos = new ManagementObjectSearcher(s, q);
var moc = mos.Get();
foreach (var managementBaseObject in moc)
{
var o = (ManagementObject)managementBaseObject;
o.InvokeMethod("WmiSetBrightness", new object[]
{
UInt32.MaxValue,
brightnessLevel
});
}
moc.Dispose();
mos.Dispose();
}
catch (ManagementException ex)
{
Logger.Instance.Error(ex);
// ignore
// it is possible that laptop lid is closed, and using external monitor
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}
#endif
}
}
}

View File

@@ -0,0 +1,35 @@
using common.libs;
namespace cmonitor.server.client.reports.llock
{
public sealed class LLockReport : IReport
{
public string Name => "LLock";
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
public Dictionary<string, object> GetReports()
{
report["Value"] = WindowHelper.GetHasWindowByName("llock.win");
return report;
}
public void Update(bool open)
{
if (open)
{
Task.Run(() =>
{
CommandHelper.Windows(string.Empty, new string[] {
$"start llock.win.exe"
});
});
}
else
{
CommandHelper.Windows(string.Empty, new string[] {
"taskkill /f /t /im \"llock.win.exe\""
});
}
}
}
}

View File

@@ -0,0 +1,311 @@
using cmonitor.server.service;
using common.libs;
using cmonitor.server.service.messengers.screen;
using System.Runtime.InteropServices;
using System.Buffers;
#if DEBUG || RELEASE
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing;
using System.IO.MemoryMappedFiles;
#endif
using System.Text;
namespace cmonitor.server.client.reports.screen
{
public sealed class ScreenReport : IReport
{
public string Name => "Screen";
private readonly ClientSignInState clientSignInState;
private readonly MessengerSender messengerSender;
private readonly Config config;
Dictionary<string, object> dic = new Dictionary<string, object> { { "LastInput", 0 }, { "UserName", string.Empty }, { "KeyBoard", string.Empty } };
public ScreenReport(ClientSignInState clientSignInState, MessengerSender messengerSender, Config config)
{
this.clientSignInState = clientSignInState;
this.messengerSender = messengerSender;
this.config = config;
if (config.IsCLient)
{
ScreenCaptureTask();
InitUserNameMemory();
InitLastInputInfo();
InitKeyBoard();
}
}
public Dictionary<string, object> GetReports()
{
dic["LastInput"] = GetLastInputInfo();
dic["UserName"] = GetUserNameMemory();
dic["KeyBoard"] = GetKeyBoard();
return dic;
}
#region
private uint screenCaptureFlag = 0;
public void Update()
{
Interlocked.CompareExchange(ref screenCaptureFlag, 1, 0);
}
private void ScreenCaptureTask()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
if (clientSignInState.Connected == true && Interlocked.CompareExchange(ref screenCaptureFlag, 0, 1) == 1)
{
try
{
await SendScreenCapture();
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
await Task.Delay(Config.ScreenTime);
}
}, TaskCreationOptions.LongRunning);
}
private async Task SendScreenCapture()
{
byte[] bytes = ScreenCapture();
if (bytes.Length > 0)
{
// byte[] bytes = MemoryPackSerializer.Serialize(img);
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)ScreenMessengerIds.Report,
Payload = bytes,
});
}
Return(bytes);
}
private byte[] ScreenCapture()
{
#if DEBUG || RELEASE
if (OperatingSystem.IsWindows())
{
IntPtr hdc = GetDC(IntPtr.Zero);
using Bitmap source = new Bitmap(GetDeviceCaps(hdc, DESKTOPHORZRES), GetDeviceCaps(hdc, DESKTOPVERTRES));
using (Graphics g = Graphics.FromImage(source))
{
g.CopyFromScreen(0, 0, 0, 0, source.Size, CopyPixelOperation.SourceCopy);
CURSORINFO pci;
pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
var hdc1 = g.GetHdc();
DrawIconEx(hdc1, pci.ptScreenPos.x - 0, pci.ptScreenPos.y - 0, pci.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL);
g.ReleaseHdc();
}
}
g.Dispose();
}
ReleaseDC(IntPtr.Zero, hdc);
int newWidth = 384;
int newHeight = (int)(source.Height * (newWidth * 1.0 / source.Width));
Bitmap bmp = new Bitmap(newWidth, newHeight);
bmp.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using Graphics graphic = Graphics.FromImage(bmp);
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.DrawImage(source, new Rectangle(0, 0, newWidth, newHeight));
using Image image = bmp;
using MemoryStream ms = new MemoryStream();
image.Save(ms, ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
byte[] bytes = ArrayPool<byte>.Shared.Rent((int)ms.Length);
ms.Read(bytes);
return bytes;
//string base64 = Convert.ToBase64String(bytes, 0, (int)ms.Length);
//ArrayPool<byte>.Shared.Return(bytes);
//return base64;
}
#endif
return Array.Empty<byte>();
}
private void Return(byte[] bytes)
{
ArrayPool<byte>.Shared.Return(bytes);
}
const int DESKTOPVERTRES = 117;
const int DESKTOPHORZRES = 118;
[DllImport("user32.dll")]
static extern IntPtr GetDC(IntPtr ptr);
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll", EntryPoint = "GetDeviceCaps", SetLastError = true)]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
private static extern int GetSystemMetrics(int mVal);
[StructLayout(LayoutKind.Sequential)]
private struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINTAPI
{
public int x;
public int y;
}
private const Int32 CURSOR_SHOWING = 0x0001;
private const Int32 DI_NORMAL = 0x0003;
[DllImport("user32.dll")]
private static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll", SetLastError = true)]
static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
#endregion
#region
private LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
private void InitLastInputInfo()
{
lastInputInfo.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
}
private uint GetLastInputInfo()
{
bool res = GetLastInputInfo(ref lastInputInfo);
if (res)
{
return (uint)Environment.TickCount - lastInputInfo.dwTime;
}
return 0;
}
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;
}
#endregion
#region
#if DEBUG || RELEASE
MemoryMappedFile mmf2;
MemoryMappedViewAccessor accessor2;
byte[] userNameBytes;
#endif
private void InitUserNameMemory()
{
#if DEBUG || RELEASE
userNameBytes = new byte[config.UserNameMemoryLength];
if (OperatingSystem.IsWindows())
{
mmf2 = MemoryMappedFile.CreateOrOpen(config.UserNameMemoryKey, userNameBytes.Length);
accessor2 = mmf2.CreateViewAccessor();
}
#endif
}
private string GetUserNameMemory()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
if (accessor2 != null)
{
accessor2.Read(0, out byte length);
if (length > 0)
{
accessor2.ReadArray(1, userNameBytes, 0, length);
return Encoding.UTF8.GetString(userNameBytes.AsSpan(0, length));
}
}
}
}
catch (Exception)
{
}
#endif
return string.Empty;
}
#endregion
#region
#if DEBUG || RELEASE
MemoryMappedFile mmf3;
MemoryMappedViewAccessor accessor3;
byte[] keyBoardBytes;
#endif
private void InitKeyBoard()
{
#if DEBUG || RELEASE
keyBoardBytes = new byte[config.KeyboardMemoryLength];
if (OperatingSystem.IsWindows())
{
mmf3 = MemoryMappedFile.CreateOrOpen(config.KeyboardMemoryKey, keyBoardBytes.Length);
accessor3 = mmf3.CreateViewAccessor();
}
#endif
}
private string GetKeyBoard()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
if (accessor3 != null)
{
accessor3.Read(0, out byte length);
if (length > 0)
{
accessor3.ReadArray(1, keyBoardBytes, 0, length);
return Encoding.UTF8.GetString(keyBoardBytes.AsSpan(0, length));
}
}
}
}
catch (Exception)
{
}
#endif
return string.Empty;
}
#endregion
}
}

View File

@@ -0,0 +1,63 @@
using System.IO.MemoryMappedFiles;
using System.Text;
namespace cmonitor.server.client.reports.screen
{
public sealed class ShareReport : IReport
{
public string Name => "Share";
private readonly Config config;
Dictionary<string, object> dic = new Dictionary<string, object> { { "Value", string.Empty}};
public ShareReport(Config config)
{
this.config = config;
InitShare();
}
public Dictionary<string, object> GetReports()
{
dic["Value"] = GetShare();
return dic;
}
MemoryMappedFile mmf3;
MemoryMappedViewAccessor accessor3;
byte[] bytes;
private void InitShare()
{
bytes = new byte[config.ShareMemoryLength];
if (OperatingSystem.IsWindows())
{
mmf3 = MemoryMappedFile.CreateOrOpen(config.ShareMemoryKey, bytes.Length);
accessor3 = mmf3.CreateViewAccessor();
}
}
private string GetShare()
{
try
{
if (OperatingSystem.IsWindows())
{
if (accessor3 != null)
{
int length = accessor3.ReadInt32(0);
if (length > 0)
{
accessor3.ReadArray(4, bytes, 0, length);
return Encoding.UTF8.GetString(bytes.AsSpan(0, length));
}
}
}
}
catch (Exception)
{
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,98 @@
using common.libs;
#if DEBUG || RELEASE
using Microsoft.Win32;
#endif
namespace cmonitor.server.client.reports.llock
{
public sealed class UsbReport : IReport
{
public string Name => "Usb";
public UsbReport()
{
UnLockUsb();
}
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
public Dictionary<string, object> GetReports()
{
return report;
}
public void Update(bool llock)
{
if (llock)
{
LockUsb();
}
else
{
UnLockUsb();
}
report["Value"] = GetHasUSBDisabled();
}
private bool GetHasUSBDisabled()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
return key != null
&& (int)key.GetValue("Start", 3, RegistryValueOptions.None) == 4;
}
}
catch (Exception ex)
{
Logger.Instance.Error($"get usb state {ex}");
}
#endif
return false;
}
private void LockUsb()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
if (key != null)
{
key.SetValue("Start", 4);
}
}
}
catch (Exception ex)
{
Logger.Instance.Error($"lock usb {ex}");
}
#endif
}
private void UnLockUsb()
{
#if DEBUG || RELEASE
try
{
if (OperatingSystem.IsWindows())
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\\CurrentControlSet\\Services\\USBSTOR", true);
if (key != null)
{
key.SetValue("Start", 3);
}
}
}
catch (Exception ex)
{
Logger.Instance.Error($"unlock usb {ex}");
}
#endif
}
}
}

View File

@@ -0,0 +1,131 @@
using common.libs;
#if DEBUG || RELEASE
using NAudio.CoreAudioApi;
#endif
namespace cmonitor.server.client.reports.volume
{
public sealed class VolumeReport : IReport
{
public string Name => "Volume";
public VolumeReport()
{
Volume();
}
public Dictionary<string, object> GetReports()
{
return new Dictionary<string, object>()
{
{ "Value",GetVolume()},
{ "Mute",GetVolumeMute()},
{ "MasterPeak",GetMasterPeakValue()},
};
}
#if DEBUG || RELEASE
MMDeviceEnumerator enumerator;
MMDevice device;
AudioEndpointVolume volumeObject;
#endif
private void Volume()
{
if (OperatingSystem.IsWindows())
{
try
{
#if DEBUG || RELEASE
enumerator = new MMDeviceEnumerator();
device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
volumeObject = device.AudioEndpointVolume;
#endif
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}
}
private float GetVolume()
{
try
{
#if DEBUG || RELEASE
if (volumeObject != null)
{
return volumeObject.MasterVolumeLevelScalar * 100;
}
#endif
}
catch (Exception)
{
}
return -1;
}
private float GetMasterPeakValue()
{
try
{
#if DEBUG || RELEASE
if (device != null)
{
return device.AudioMeterInformation.MasterPeakValue * 100;
}
#endif
}
catch (Exception)
{
}
return -1;
}
private bool GetVolumeMute()
{
try
{
#if DEBUG || RELEASE
if (volumeObject != null)
{
return volumeObject.Mute;
}
#endif
}
catch (Exception)
{
}
return false;
}
public void SetVolume(float volume)
{
try
{
#if DEBUG || RELEASE
volume = Math.Max(0, Math.Min(1, volume));
if (volumeObject != null)
{
volumeObject.MasterVolumeLevelScalar = volume;
}
#endif
}
catch (Exception)
{
}
}
public void SetVolumeMute(bool mute)
{
try
{
#if DEBUG || RELEASE
if (volumeObject != null)
{
volumeObject.Mute = mute;
}
#endif
}
catch (Exception)
{
}
}
}
}

View File

@@ -0,0 +1,41 @@
using common.libs;
namespace cmonitor.server.client.reports.llock
{
public sealed class WallpaperReport : IReport
{
public string Name => "Wallpaper";
private Dictionary<string, object> report = new Dictionary<string, object>() { { "Value", false } };
private readonly Config config;
public WallpaperReport(Config config)
{
this.config = config;
}
public Dictionary<string, object> GetReports()
{
report["Value"] = WindowHelper.GetHasWindowByName("wallpaper.win");
return report;
}
public void Update(bool open, string url)
{
if (open)
{
Task.Run(() =>
{
CommandHelper.Windows(string.Empty, new string[] {
$"start wallpaper.win.exe \"{url}\" {config.KeyboardMemoryKey} {config.KeyboardMemoryLength}"
});
});
}
else
{
CommandHelper.Windows(string.Empty, new string[] {
"taskkill /f /t /im \"wallpaper.win.exe\""
});
}
}
}
}

View File

@@ -0,0 +1,342 @@
using common.libs;
using common.libs.extends;
using System.Buffers;
using System.Net;
using System.Net.Sockets;
namespace cmonitor.server.service
{
/// <summary>
/// 连接对象
/// </summary>
public interface IConnection
{
public string Name{ get; set; }
/// <summary>
/// <summary>
/// 已连接
/// </summary>
public bool Connected { get; }
public IPEndPoint Address { get; }
#region
/// <summary>
/// 请求数据包装对象
/// </summary>
public MessageRequestWrap ReceiveRequestWrap { get; }
/// <summary>
/// 回复数据包装对象
/// </summary>
public MessageResponseWrap ReceiveResponseWrap { get; }
/// <summary>
/// 接收到的原始数据
/// </summary>
public Memory<byte> ReceiveData { get; set; }
#endregion
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public Task<bool> Send(ReadOnlyMemory<byte> data, bool unconnectedMessage = false);
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public Task<bool> Send(byte[] data, int length, bool unconnectedMessage = false);
/// <summary>
/// 销毁
/// </summary>
public void Disponse();
#region
public Memory<byte> ResponseData { get; }
public void Write(Memory<byte> data);
public void Write(ulong num);
public void Write(ushort num);
public void Write(ushort[] nums);
/// <summary>
/// 英文多用这个
/// </summary>
/// <param name="str"></param>
public void WriteUTF8(string str);
/// <summary>
/// 中文多用这个
/// </summary>
/// <param name="str"></param>
public void WriteUTF16(string str);
/// <summary>
/// 归还池
/// </summary>
public void Return();
#endregion
public Task WaitOne();
public void Release();
}
public abstract class Connection : IConnection
{
public Connection()
{
}
public string Name { get; set; }
/// <summary>
/// 已连接
/// </summary>
public virtual bool Connected => false;
/// <summary>
/// 地址
/// </summary>
public IPEndPoint Address { get; protected set; }
#region
/// <summary>
/// 接收请求数据
/// </summary>
public MessageRequestWrap ReceiveRequestWrap { get; set; }
/// <summary>
/// 接收回执数据
/// </summary>
public MessageResponseWrap ReceiveResponseWrap { get; set; }
/// <summary>
/// 接收数据
/// </summary>
public Memory<byte> ReceiveData { get; set; }
#endregion
#region
public Memory<byte> ResponseData { get; private set; }
private byte[] responseData;
private int length = 0;
public void Write(Memory<byte> data)
{
ResponseData = data;
}
public void Write(ulong num)
{
length = 8;
responseData = ArrayPool<byte>.Shared.Rent(length);
num.ToBytes(responseData);
ResponseData = responseData.AsMemory(0, length);
}
public void Write(ushort num)
{
length = 2;
responseData = ArrayPool<byte>.Shared.Rent(length);
num.ToBytes(responseData);
ResponseData = responseData.AsMemory(0, length);
}
public void Write(ushort[] nums)
{
length = nums.Length * 2;
responseData = ArrayPool<byte>.Shared.Rent(length);
nums.ToBytes(responseData);
ResponseData = responseData.AsMemory(0, length);
}
/// <summary>
/// 英文多用这个
/// </summary>
/// <param name="str"></param>
public void WriteUTF8(string str)
{
var span = str.AsSpan();
responseData = ArrayPool<byte>.Shared.Rent((span.Length + 1) * 3 + 8);
var memory = responseData.AsMemory();
int utf8Length = span.ToUTF8Bytes(memory.Slice(8));
span.Length.ToBytes(memory);
utf8Length.ToBytes(memory.Slice(4));
length = utf8Length + 8;
ResponseData = responseData.AsMemory(0, length);
}
/// <summary>
/// 中文多用这个
/// </summary>
/// <param name="str"></param>
public void WriteUTF16(string str)
{
var span = str.GetUTF16Bytes();
length = span.Length + 4;
responseData = ArrayPool<byte>.Shared.Rent(length);
str.Length.ToBytes(responseData);
span.CopyTo(responseData.AsSpan(4));
ResponseData = responseData.AsMemory(0, length);
}
/// <summary>
/// 归还池
/// </summary>
public void Return()
{
if (length > 0 && ResponseData.Length > 0)
{
ArrayPool<byte>.Shared.Return(responseData);
}
ResponseData = Helper.EmptyArray;
responseData = null;
length = 0;
}
#endregion
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public abstract Task<bool> Send(ReadOnlyMemory<byte> data, bool logger = false);
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public abstract Task<bool> Send(byte[] data, int length, bool logger = false);
/// <summary>
/// 销毁
/// </summary>
public virtual void Disponse()
{
try
{
if (Semaphore != null)
{
if (locked)
{
locked = false;
Semaphore.Release();
}
Semaphore.Dispose();
}
Semaphore = null;
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
//ReceiveRequestWrap = null;
//ReceiveResponseWrap = null;
}
SemaphoreSlim Semaphore = new SemaphoreSlim(1);
bool locked = false;
public virtual async Task WaitOne()
{
try
{
if (Semaphore != null)
{
locked = true;
await Semaphore.WaitAsync();
}
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}
public virtual void Release()
{
try
{
if (Semaphore != null)
{
locked = false;
Semaphore.Release();
}
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}
}
public sealed class TcpConnection : Connection
{
public TcpConnection(Socket tcpSocket) : base()
{
TcpSocket = tcpSocket;
IPEndPoint address = TcpSocket.RemoteEndPoint as IPEndPoint ?? new IPEndPoint(IPAddress.Any, 0);
if (address.Address.AddressFamily == AddressFamily.InterNetworkV6 && address.Address.IsIPv4MappedToIPv6)
{
address = new IPEndPoint(new IPAddress(address.Address.GetAddressBytes()[^4..]), address.Port);
}
Address = address;
}
/// <summary>
/// 已连接
/// </summary>
public override bool Connected => TcpSocket != null && TcpSocket.Connected;
/// <summary>
/// socket
/// </summary>
public Socket TcpSocket { get; private set; }
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public override async Task<bool> Send(ReadOnlyMemory<byte> data, bool unconnectedMessage = false)
{
if (Connected)
{
try
{
await TcpSocket.SendAsync(data, SocketFlags.None);
//SentBytes += (ulong)data.Length;
return true;
}
catch (Exception ex)
{
Disponse();
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
}
return false;
}
/// <summary>
/// 发送
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public override async Task<bool> Send(byte[] data, int length, bool unconnectedMessage = false)
{
return await Send(data.AsMemory(0, length), unconnectedMessage);
}
/// <summary>
/// 销毁
/// </summary>
public override void Disponse()
{
base.Disponse();
if (TcpSocket != null)
{
TcpSocket.SafeClose();
TcpSocket.Dispose();
}
}
}
}

View File

@@ -0,0 +1,63 @@
namespace cmonitor.server.service
{
/// <summary>
/// 消息接口
/// </summary>
public interface IMessenger
{
}
/// <summary>
/// 消息id范围
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class MessengerIdRangeAttribute : Attribute
{
/// <summary>
/// 最小
/// </summary>
public ushort Min { get; set; }
/// <summary>
/// 最大
/// </summary>
public ushort Max { get; set; }
/// <summary>
///
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
public MessengerIdRangeAttribute(ushort min, ushort max)
{
Min = min;
Max = max;
}
}
/// <summary>
/// 消息id
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MessengerIdAttribute : Attribute
{
/// <summary>
/// id
/// </summary>
public ushort Id { get; set; }
/// <summary>
///
/// </summary>
/// <param name="id"></param>
public MessengerIdAttribute(ushort id)
{
Id = id;
}
}
/// <summary>
/// 消息
/// </summary>
[AttributeUsage(AttributeTargets.Enum)]
public sealed class MessengerIdEnumAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,162 @@
using common.libs;
using common.libs.extends;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using System.Security.Cryptography;
namespace cmonitor.server.service
{
/// <summary>
/// 消息处理总线
/// </summary>
public sealed class MessengerResolver
{
delegate void VoidDelegate(IConnection connection);
delegate Task TaskDelegate(IConnection connection);
private readonly Dictionary<ushort, MessengerCacheInfo> messengers = new();
private readonly TcpServer tcpserver;
private readonly MessengerSender messengerSender;
private readonly ServiceProvider serviceProvider;
public MessengerResolver(TcpServer tcpserver, MessengerSender messengerSender, ServiceProvider serviceProvider)
{
this.tcpserver = tcpserver;
this.messengerSender = messengerSender;
this.tcpserver.OnPacket = InputData;
this.serviceProvider = serviceProvider;
}
public void LoadMessenger(Assembly[] assemblys)
{
Type voidType = typeof(void);
Type midType = typeof(MessengerIdAttribute);
foreach (Type type in ReflectionHelper.GetInterfaceSchieves(assemblys, typeof(IMessenger)).Distinct())
{
object obj = serviceProvider.GetService(type);
foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
MessengerIdAttribute mid = method.GetCustomAttribute(midType) as MessengerIdAttribute;
if (mid != null)
{
if (messengers.ContainsKey(mid.Id) == false)
{
MessengerCacheInfo cache = new MessengerCacheInfo
{
Target = obj
};
if (method.ReturnType == voidType)
{
cache.VoidMethod = (VoidDelegate)Delegate.CreateDelegate(typeof(VoidDelegate), obj, method);
}
else if (method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null)
{
cache.TaskMethod = (TaskDelegate)Delegate.CreateDelegate(typeof(TaskDelegate), obj, method);
}
messengers.TryAdd(mid.Id, cache);
}
else
{
Logger.Instance.Error($"{type.Name}->{method.Name}->{mid.Id} 消息id已存在");
}
}
}
}
}
/// <summary>
/// 收到消息
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
public async Task InputData(IConnection connection)
{
Memory<byte> receive = connection.ReceiveData;
//去掉表示数据长度的4字节
Memory<byte> readReceive = receive.Slice(4);
MessageResponseWrap responseWrap = connection.ReceiveResponseWrap;
MessageRequestWrap requestWrap = connection.ReceiveRequestWrap;
try
{
//回复的消息
if ((MessageTypes)(readReceive.Span[0] & 0b0000_1111) == MessageTypes.RESPONSE)
{
responseWrap.FromArray(readReceive);
messengerSender.Response(responseWrap);
return;
}
//新的请求
requestWrap.FromArray(readReceive);
//404,没这个插件
if (messengers.TryGetValue(requestWrap.MessengerId, out MessengerCacheInfo plugin) == false)
{
if (requestWrap.Reply == true)
{
bool res = await messengerSender.ReplyOnly(new MessageResponseWrap
{
Connection = connection,
Code = MessageResponeCodes.NOT_FOUND,
RequestId = requestWrap.RequestId
}).ConfigureAwait(false);
}
return;
}
if (plugin.VoidMethod != null)
{
plugin.VoidMethod(connection);
}
else if (plugin.TaskMethod != null)
{
await plugin.TaskMethod(connection);
}
if (requestWrap.Reply == true)
{
bool res = await messengerSender.ReplyOnly(new MessageResponseWrap
{
Connection = connection,
Payload = connection.ResponseData,
RequestId = requestWrap.RequestId
}).ConfigureAwait(false);
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
finally
{
connection.Return();
}
}
/// <summary>
/// 消息插件缓存
/// </summary>
private struct MessengerCacheInfo
{
/// <summary>
/// 对象
/// </summary>
public object Target { get; set; }
/// <summary>
/// 空返回方法
/// </summary>
public VoidDelegate VoidMethod { get; set; }
/// <summary>
/// Task返回方法
/// </summary>
public TaskDelegate TaskMethod { get; set; }
}
}
}

View File

@@ -0,0 +1,156 @@
using common.libs;
using System.Collections.Concurrent;
namespace cmonitor.server.service
{
/// <summary>
/// 消息发送器
/// </summary>
public sealed class MessengerSender
{
public NumberSpaceUInt32 requestIdNumberSpace = new NumberSpaceUInt32(0);
private WheelTimer<TimeoutState> wheelTimer = new WheelTimer<TimeoutState>();
private ConcurrentDictionary<uint, WheelTimerTimeout<TimeoutState>> sends = new ConcurrentDictionary<uint, WheelTimerTimeout<TimeoutState>>();
public MessengerSender()
{
}
/// <summary>
/// 发送并等待回复
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public async Task<MessageResponeInfo> SendReply(MessageRequestWrap msg)
{
if (msg.Connection == null || msg.Connection.Connected == false)
{
return new MessageResponeInfo { Code = MessageResponeCodes.NOT_CONNECT };
}
if (msg.RequestId == 0)
{
uint id = msg.RequestId;
Interlocked.CompareExchange(ref id, requestIdNumberSpace.Increment(), 0);
msg.RequestId = id;
}
WheelTimerTimeout<TimeoutState> timeout = NewReply(msg);
bool res = await SendOnly(msg).ConfigureAwait(false);
if (res == false)
{
sends.TryRemove(msg.RequestId, out _);
timeout.Cancel();
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.NOT_CONNECT });
}
return await timeout.Task.State.Tcs.Task.ConfigureAwait(false);
}
/// <summary>
/// 只发送,不等回复
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public async Task<bool> SendOnly(MessageRequestWrap msg)
{
if (msg.Connection == null || msg.Connection.Connected == false)
{
return false;
}
try
{
if (msg.RequestId == 0)
{
uint id = msg.RequestId;
Interlocked.CompareExchange(ref id, requestIdNumberSpace.Increment(), 0);
msg.RequestId = id;
}
byte[] bytes = msg.ToArray(out int length);
bool res = await msg.Connection.Send(bytes.AsMemory(0, length)).ConfigureAwait(false);
msg.Return(bytes);
return res;
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
return false;
}
/// <summary>
/// 回复远程消息,收到某个连接的消息后,通过这个再返回消息给它
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public async ValueTask<bool> ReplyOnly(MessageResponseWrap msg)
{
if (msg.Connection == null)
{
return false;
}
try
{
byte[] bytes = msg.ToArray(out int length);
bool res = await msg.Connection.Send(bytes.AsMemory(0, length)).ConfigureAwait(false);
msg.Return(bytes);
return res;
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
return false;
}
/// <summary>
/// 回复本地消息发送消息后socket收到消息通过这个方法回复给刚刚发送的对象
/// </summary>
/// <param name="wrap"></param>
public void Response(MessageResponseWrap wrap)
{
if (sends.TryRemove(wrap.RequestId, out WheelTimerTimeout<TimeoutState> timeout))
{
timeout.Cancel();
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = wrap.Code, Data = wrap.Payload });
}
}
private WheelTimerTimeout<TimeoutState> NewReply(MessageRequestWrap msg)
{
msg.Reply = true;
if (msg.Timeout <= 0)
{
msg.Timeout = 15000;
}
WheelTimerTimeout<TimeoutState> timeout = wheelTimer.NewTimeout(new WheelTimerTimeoutTask<TimeoutState>
{
Callback = TimeoutCallback,
State = new TimeoutState { RequestId = msg.RequestId, Tcs = new TaskCompletionSource<MessageResponeInfo>() }
}, msg.Timeout);
sends.TryAdd(msg.RequestId, timeout);
return timeout;
}
private void TimeoutCallback(WheelTimerTimeout<TimeoutState> timeout)
{
sends.TryRemove(timeout.Task.State.RequestId, out _);
timeout.Task.State.Tcs.SetResult(new MessageResponeInfo { Code = MessageResponeCodes.TIMEOUT });
}
}
public sealed class MessageResponeInfo
{
public MessageResponeCodes Code { get; set; }
public ReadOnlyMemory<byte> Data { get; set; }
}
public sealed class TimeoutState
{
public uint RequestId { get; set; }
public TaskCompletionSource<MessageResponeInfo> Tcs { get; set; }
}
}

View File

@@ -0,0 +1,185 @@
using common.libs.extends;
using System.Buffers;
using System.ComponentModel;
namespace cmonitor.server.service
{
public sealed class MessageRequestWrap
{
public int Timeout { get; set; }
public ushort MessengerId { get; set; }
public uint RequestId { get; set; }
public bool Reply { get; internal set; }
public Memory<byte> Payload { get; set; }
public IConnection Connection { get; set; }
public byte[] ToArray(out int length)
{
int index = 0;
length = 4
+ 1 //Reply + type
+ 4
+ 2
+ Payload.Length;
byte[] res = ArrayPool<byte>.Shared.Rent(length);
((uint)length - 4).ToBytes(res);
index += 4;
res[index] = (byte)((Reply ? 1 : 0) << 4 | (byte)MessageTypes.REQUEST);
index += 1;
RequestId.ToBytes(res.AsMemory(index));
index += 4;
MessengerId.ToBytes(res.AsMemory(index));
index += 2;
Payload.CopyTo(res.AsMemory(index, Payload.Length));
index += Payload.Length;
return res;
}
public unsafe void FromArray(Memory<byte> memory)
{
var span = memory.Span;
int index = 0;
Reply = span[index] >> 4 == 1;
index += 1;
RequestId = span.Slice(index).ToUInt32();
index += 4;
MessengerId = span.Slice(index).ToUInt16();
index += 2;
Payload = memory.Slice(index, memory.Length - index);
}
public void Return(byte[] array)
{
ArrayPool<byte>.Shared.Return(array);
}
}
/// <summary>
/// 回执消息包
/// </summary>
public sealed class MessageResponseWrap
{
/// <summary>
///
/// </summary>
public IConnection Connection { get; set; }
/// <summary>
///
/// </summary>
public MessageResponeCodes Code { get; set; }
/// <summary>
///
/// </summary>
public uint RequestId { get; set; }
/// <summary>
///
/// </summary>
public Memory<byte> Payload { get; set; }
/// <summary>
/// 转包
/// </summary>
/// <returns></returns>
public byte[] ToArray(out int length)
{
length = 4
+ 1 //type
+ 1 //code
+ 4 //requestid
+ Payload.Length;
byte[] res = ArrayPool<byte>.Shared.Rent(length);
int index = 0;
((uint)length - 4).ToBytes(res);
index += 4;
res[index] = (byte)MessageTypes.RESPONSE;
index += 1;
res[index] = (byte)Code;
index += 1;
RequestId.ToBytes(res.AsMemory(index));
index += 4;
if (Payload.Length > 0)
{
Payload.CopyTo(res.AsMemory(index, Payload.Length));
index += Payload.Length;
}
return res;
}
/// <summary>
/// 解包
/// </summary>
/// <param name="memory"></param>
public void FromArray(Memory<byte> memory)
{
var span = memory.Span;
int index = 0;
index += 1;
Code = (MessageResponeCodes)span[index];
index += 1;
RequestId = span.Slice(index).ToUInt32();
index += 4;
Payload = memory.Slice(index, memory.Length - index);
}
public void Return(byte[] array)
{
ArrayPool<byte>.Shared.Return(array);
}
}
/// <summary>
/// 消息状态
/// </summary>
[Flags]
public enum MessageResponeCodes : byte
{
[Description("成功")]
OK = 0,
[Description("网络未连接")]
NOT_CONNECT = 1,
[Description("网络资源未找到")]
NOT_FOUND = 2,
[Description("网络超时")]
TIMEOUT = 3,
[Description("程序错误")]
ERROR = 4,
}
/// <summary>
/// 消息类别
/// </summary>
[Flags]
public enum MessageTypes : byte
{
[Description("请求")]
REQUEST = 0,
[Description("回复")]
RESPONSE = 1
}
}

View File

@@ -0,0 +1,316 @@
using common.libs;
using common.libs.extends;
using System.Net;
using System.Net.Sockets;
namespace cmonitor.server.service
{
public sealed class TcpServer
{
private int bufferSize = 8 * 1024;
private Socket socket;
private UdpClient socketUdp;
private CancellationTokenSource cancellationTokenSource;
public Func<IConnection, Task> OnPacket { get; set; } = async (connection) => { await Task.CompletedTask; };
private readonly Config config;
public TcpServer(Config config)
{
this.config = config;
}
public void Start()
{
if (socket == null)
{
cancellationTokenSource = new CancellationTokenSource();
socket = BindAccept();
}
}
private Socket BindAccept()
{
IPEndPoint localEndPoint = new IPEndPoint(NetworkHelper.IPv6Support ? IPAddress.IPv6Any : IPAddress.Any, config.ServicePort);
Socket socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.IPv6Only(localEndPoint.AddressFamily, false);
socket.ReuseBind(localEndPoint);
socket.Listen(int.MaxValue);
SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs
{
UserToken = new AsyncUserToken
{
Socket = socket
},
SocketFlags = SocketFlags.None,
};
acceptEventArg.Completed += IO_Completed;
StartAccept(acceptEventArg);
socketUdp = new UdpClient(new IPEndPoint(IPAddress.Any, config.ServicePort));
//socketUdp.JoinMulticastGroup(config.BroadcastIP);
socketUdp.Client.EnableBroadcast = true;
socketUdp.Client.WindowsUdpBug();
IAsyncResult result = socketUdp.BeginReceive(ReceiveCallbackUdp, null);
return socket;
}
private async void ReceiveCallbackUdp(IAsyncResult result)
{
try
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort);
byte[] bytes = socketUdp.EndReceive(result, ref endPoint);
try
{
IPHostEntry entry = Dns.GetHostEntry(Dns.GetHostName());
List<IPAddress> ips = entry.AddressList.Where(c => c.AddressFamily == AddressFamily.InterNetwork).Distinct().ToList();
Dictionary<IPAddress, BroadcastEndpointInfo> dic = new Dictionary<IPAddress, BroadcastEndpointInfo>();
foreach (var item in ips)
{
dic.Add(item, new BroadcastEndpointInfo
{
Web = config.WebPort,
Api = config.ApiPort,
Service = config.ServicePort
});
}
await socketUdp.SendAsync(dic.ToJson().ToBytes(), endPoint);
}
catch (Exception)
{
}
result = socketUdp.BeginReceive(ReceiveCallbackUdp, null);
}
catch (Exception)
{
}
}
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
acceptEventArg.AcceptSocket = null;
AsyncUserToken token = (AsyncUserToken)acceptEventArg.UserToken;
try
{
if (token.Socket.AcceptAsync(acceptEventArg) == false)
{
ProcessAccept(acceptEventArg);
}
}
catch (Exception)
{
token.Clear();
}
}
private void IO_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Accept:
ProcessAccept(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
default:
break;
}
}
private void ProcessAccept(SocketAsyncEventArgs e)
{
if (e.AcceptSocket != null)
{
e.AcceptSocket.KeepAlive();
BindReceive(e.AcceptSocket);
StartAccept(e);
}
}
public IConnection BindReceive(Socket socket)
{
try
{
if (socket == null || socket.RemoteEndPoint == null)
{
return null;
}
AsyncUserToken userToken = new AsyncUserToken
{
Socket = socket,
Connection = CreateConnection(socket)
};
SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs
{
UserToken = userToken,
SocketFlags = SocketFlags.None,
};
userToken.PoolBuffer = new byte[bufferSize];
readEventArgs.SetBuffer(userToken.PoolBuffer, 0, bufferSize);
readEventArgs.Completed += IO_Completed;
if (socket.ReceiveAsync(readEventArgs) == false)
{
ProcessReceive(readEventArgs);
}
return userToken.Connection;
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
}
return null;
}
private async void ProcessReceive(SocketAsyncEventArgs e)
{
try
{
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
int offset = e.Offset;
int length = e.BytesTransferred;
await ReadPacket(token, e.Buffer, offset, length);
if (token.Socket.Available > 0)
{
while (token.Socket.Available > 0)
{
length = token.Socket.Receive(e.Buffer);
if (length > 0)
{
await ReadPacket(token, e.Buffer, 0, length);
}
else
{
CloseClientSocket(e);
return;
}
}
}
if (token.Socket.Connected == false)
{
CloseClientSocket(e);
return;
}
if (token.Socket.ReceiveAsync(e) == false)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
catch (Exception ex)
{
if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG)
Logger.Instance.Error(ex);
CloseClientSocket(e);
}
}
private async Task ReadPacket(AsyncUserToken token, byte[] data, int offset, int length)
{
//是一个完整的包
if (token.DataBuffer.Size == 0 && length > 4)
{
Memory<byte> memory = data.AsMemory(offset, length);
int packageLen = memory.Span.ToInt32();
if (packageLen == length - 4)
{
token.Connection.ReceiveData = data.AsMemory(offset, packageLen + 4);
await OnPacket(token.Connection);
return;
}
}
//不是完整包
token.DataBuffer.AddRange(data, offset, length);
do
{
int packageLen = token.DataBuffer.Data.Span.ToInt32();
if (packageLen > token.DataBuffer.Size - 4)
{
break;
}
token.Connection.ReceiveData = token.DataBuffer.Data.Slice(0, packageLen + 4);
await OnPacket(token.Connection);
token.DataBuffer.RemoveRange(0, packageLen + 4);
} while (token.DataBuffer.Size > 4);
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
if (token.Socket != null)
{
token.Clear();
e.Dispose();
}
}
public IConnection CreateConnection(Socket socket)
{
return new TcpConnection(socket)
{
ReceiveRequestWrap = new MessageRequestWrap(),
ReceiveResponseWrap = new MessageResponseWrap()
};
}
public void Stop()
{
cancellationTokenSource?.Cancel();
socket?.SafeClose();
socket = null;
}
public void Disponse()
{
Stop();
OnPacket = null;
}
}
public sealed class AsyncUserToken
{
public IConnection Connection { get; set; }
public Socket Socket { get; set; }
public ReceiveDataBuffer DataBuffer { get; set; } = new ReceiveDataBuffer();
public byte[] PoolBuffer { get; set; }
public void Clear()
{
Socket?.SafeClose();
Socket = null;
PoolBuffer = Helper.EmptyArray;
DataBuffer.Clear(true);
GC.Collect();
// GC.SuppressFinalize(this);
}
}
public sealed class BroadcastEndpointInfo
{
public int Web { get; set; }
public int Api { get; set; }
public int Service { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using cmonitor.server.api;
using cmonitor.server.client;
using cmonitor.server.client.reports.active;
using cmonitor.server.service.messengers.sign;
using common.libs;
using MemoryPack;
namespace cmonitor.server.service.messengers.active
{
public sealed class ActiveMessenger : IMessenger
{
private readonly ActiveWindowReport activeWindowReport;
public ActiveMessenger(ActiveWindowReport activeWindowReport)
{
this.activeWindowReport = activeWindowReport;
}
[MessengerId((ushort)ActiveMessengerIds.Get)]
public void Get(IConnection connection)
{
connection.Write(MemoryPackSerializer.Serialize(activeWindowReport.GetActiveWindowTimes()));
}
[MessengerId((ushort)ActiveMessengerIds.Clear)]
public void Clear(IConnection connection)
{
activeWindowReport.ClearActiveWindowTimes();
connection.Write(Helper.TrueArray);
}
[MessengerId((ushort)ActiveMessengerIds.Disallow)]
public void Disallow(IConnection connection)
{
activeWindowReport.DisallowRun(MemoryPackSerializer.Deserialize<string[]>(connection.ReceiveRequestWrap.Payload.Span));
connection.Write(Helper.TrueArray);
}
}
}

View File

@@ -0,0 +1,11 @@
namespace cmonitor.server.service.messengers.active
{
public enum ActiveMessengerIds : ushort
{
Get = 400,
Clear = 401,
Disallow = 402,
None = 499
}
}

View File

@@ -0,0 +1,29 @@
using cmonitor.server.service.messengers.command;
using common.libs;
using MemoryPack;
namespace cmonitor.server.service.messengers.report
{
public sealed class CommandMessenger : IMessenger
{
public CommandMessenger()
{
}
[MessengerId((ushort)CommandMessengerIds.Exec)]
public void Exec(IConnection connection)
{
string[] commands = MemoryPackSerializer.Deserialize<string[]>(connection.ReceiveRequestWrap.Payload.Span);
Task.Run(() =>
{
CommandHelper.Windows(string.Empty, commands);
});
connection.Write(Helper.TrueArray);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.command
{
public enum CommandMessengerIds : ushort
{
Exec = 200,
None = 299,
}
}

View File

@@ -0,0 +1,63 @@
using cmonitor.hijack;
using MemoryPack;
namespace cmonitor.server.service.messengers.hijack
{
public sealed class HijackMessenger : IMessenger
{
private readonly HijackConfig hijackConfig;
private readonly HijackController hijackController;
public HijackMessenger(HijackConfig hijackConfig, HijackController hijackController)
{
this.hijackConfig = hijackConfig;
this.hijackController = hijackController;
}
[MessengerId((ushort)HijackMessengerIds.Update)]
public void Update(IConnection connection)
{
SetRuleInfo info = MemoryPackSerializer.Deserialize<SetRuleInfo>(connection.ReceiveRequestWrap.Payload.Span);
hijackConfig.AllowDomains = info.AllowDomains;
hijackConfig.DeniedDomains = info.DeniedDomains;
hijackConfig.AllowProcesss = info.AllowProcesss;
hijackConfig.DeniedProcesss = info.DeniedProcesss;
hijackConfig.AllowIPs = info.AllowIPs;
hijackConfig.DeniedIPs = info.DeniedIPs;
hijackController.SetRules();
}
}
[MemoryPackable]
public sealed partial class SetRuleInfo
{
/// <summary>
/// 进程白名单
/// </summary>
public string[] AllowProcesss { get; set; } = Array.Empty<string>();
/// <summary>
/// 进程黑名单
/// </summary>
public string[] DeniedProcesss { get; set; } = Array.Empty<string>();
/// <summary>
/// 域名白名单
/// </summary>
public string[] AllowDomains { get; set; } = Array.Empty<string>();
/// <summary>
/// 域名黑名单
/// </summary>
public string[] DeniedDomains { get; set; } = Array.Empty<string>();
/// <summary>
/// ip白名单
/// </summary>
public string[] AllowIPs { get; set; } = Array.Empty<string>();
/// <summary>
/// ip黑名单
/// </summary>
public string[] DeniedIPs { get; set; } = Array.Empty<string>();
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.hijack
{
public enum HijackMessengerIds : ushort
{
Update = 300,
None = 399
}
}

View File

@@ -0,0 +1,23 @@
using cmonitor.server.client.reports.light;
using MemoryPack;
namespace cmonitor.server.service.messengers.light
{
public sealed class LightMessenger : IMessenger
{
private readonly LightReport lightReport;
public LightMessenger(LightReport lightReport)
{
this.lightReport = lightReport;
}
[MessengerId((ushort)LightMessengerIds.Update)]
public void Update(IConnection connection)
{
int value = MemoryPackSerializer.Deserialize<int>(connection.ReceiveRequestWrap.Payload.Span);
lightReport.SetLight(value);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.light
{
public enum LightMessengerIds : ushort
{
Update = 1000,
None = 1099
}
}

View File

@@ -0,0 +1,23 @@
using cmonitor.server.client.reports.llock;
using cmonitor.server.service.messengers.sign;
using MemoryPack;
namespace cmonitor.server.service.messengers.llock
{
public sealed class LLockMessenger : IMessenger
{
private readonly LLockReport lLockReport;
public LLockMessenger(LLockReport lLockReport)
{
this.lLockReport = lLockReport;
}
[MessengerId((ushort)LLockMessengerIds.Update)]
public void Update(IConnection connection)
{
lLockReport.Update(MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span));
}
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.llock
{
public enum LLockMessengerIds : ushort
{
Update = 500,
None = 599
}
}

View File

@@ -0,0 +1,49 @@
using cmonitor.server.api;
using cmonitor.server.client.reports;
using cmonitor.server.service.messengers.sign;
using common.libs;
using MemoryPack;
namespace cmonitor.server.service.messengers.report
{
public sealed class ReportMessenger : IMessenger
{
private readonly IClientServer clientServer;
private readonly ReportTransfer reportTransfer;
public ReportMessenger(IClientServer clientServer , ReportTransfer reportTransfer)
{
this.clientServer = clientServer;
this.reportTransfer = reportTransfer;
}
[MessengerId((ushort)ReportMessengerIds.Update)]
public void Update(IConnection connection)
{
reportTransfer.Update();
connection.Write(Helper.TrueArray);
}
[MessengerId((ushort)ReportMessengerIds.Report)]
public void Report(IConnection connection)
{
string report = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
clientServer.Notify("/notify/report/report", new { connection.Name, Report = report });
}
[MessengerId((ushort)ReportMessengerIds.Ping)]
public void Ping(IConnection connection)
{
connection.Write(Helper.TrueArray);
}
[MessengerId((ushort)ReportMessengerIds.Pong)]
public void Pong(IConnection connection)
{
connection.Write(Helper.TrueArray);
}
}
}

View File

@@ -0,0 +1,11 @@
namespace cmonitor.server.service.messengers.sign
{
public enum ReportMessengerIds : ushort
{
Report = 100,
Update = 101,
Ping = 102,
Pong = 103,
None = 199
}
}

View File

@@ -0,0 +1,48 @@
using cmonitor.server.api;
using cmonitor.server.client.reports.screen;
using cmonitor.server.service.messengers.sign;
using MemoryPack;
namespace cmonitor.server.service.messengers.screen
{
public sealed class ScreenMessenger : IMessenger
{
private readonly ScreenReport screenReport;
private readonly IClientServer clientServer;
private readonly Config config;
private readonly SignCaching signCaching;
public ScreenMessenger(ScreenReport screenReport, IClientServer clientServer, Config config, SignCaching signCaching)
{
this.screenReport = screenReport;
this.clientServer = clientServer;
this.config = config;
this.signCaching = signCaching;
}
[MessengerId((ushort)ScreenMessengerIds.Update)]
public void Update(IConnection connection)
{
screenReport.Update();
}
[MessengerId((ushort)ScreenMessengerIds.Report)]
public void Report(IConnection connection)
{
if (signCaching.Get(connection.Name, out SignCacheInfo cache))
{
if (cache.Version == config.Version)
{
clientServer.Notify("/notify/report/screen", connection.Name, connection.ReceiveRequestWrap.Payload);
//clientServer.Notify("/notify/report/screen", new { connection.Name, Img = connection.ReceiveRequestWrap.Payload.ToArray() });
}
else
{
string base64 = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
clientServer.Notify("/notify/report/screen", new { connection.Name, Img = base64 });
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace cmonitor.server.service.messengers.screen
{
public enum ScreenMessengerIds : ushort
{
Update = 800,
Report = 801,
None = 899
}
}

View File

@@ -0,0 +1,131 @@
using common.libs.database;
using MemoryPack;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
namespace cmonitor.server.service.messengers.sign
{
public sealed class SignCaching
{
private readonly IConfigDataProvider<SignCacheFileInfo> configDataProvider;
private SignCacheFileInfo config;
private bool changed = false;
public SignCaching(IConfigDataProvider<SignCacheFileInfo> configDataProvider)
{
this.configDataProvider = configDataProvider;
config = configDataProvider.Load().Result ?? new SignCacheFileInfo();
SaveConfig();
}
public void Sign(IConnection connection, SignInfo signInfo)
{
if (config.Clients.TryRemove(signInfo.MachineName, out SignCacheInfo cache))
{
cache.Connection?.Disponse();
}
connection.Name = signInfo.MachineName;
cache = new SignCacheInfo
{
Connection = connection,
MachineName = signInfo.MachineName,
Version = signInfo.Version
};
config.Clients.TryAdd(signInfo.MachineName, cache);
changed = true;
}
public bool Get(string machineName, out SignCacheInfo cache)
{
return config.Clients.TryGetValue(machineName, out cache);
}
public List<SignCacheInfo> Get()
{
return config.Clients.Values.ToList();
}
public bool Del(string machineName)
{
bool res = config.Clients.TryRemove(machineName, out _);
changed = true;
return true;
}
private void SaveConfig()
{
Task.Factory.StartNew(() =>
{
while (true)
{
if (changed == true)
{
changed = false;
configDataProvider.Save(config).Wait();
}
Thread.Sleep(5000);
}
}, TaskCreationOptions.LongRunning);
}
}
[Table("sign-cache")]
public sealed class SignCacheFileInfo
{
public ConcurrentDictionary<string, SignCacheInfo> Clients { get; set; } = new ConcurrentDictionary<string, SignCacheInfo>();
}
public sealed class SignCacheInfo
{
public string MachineName { get; set; }
public string Version { get; set; } = "1.0.0.0";
[JsonIgnore]
public int ReportFlag = 1;
[JsonIgnore]
public int ReportTime = Environment.TickCount;
[JsonIgnore]
public int PingFlag = 1;
[JsonIgnore]
public int ScreenFlag = 1;
[JsonIgnore]
public int ScreenTime = Environment.TickCount;
public bool GetReport()
{
return Environment.TickCount - ReportTime > Config.ReportTime;
}
public void UpdateReport()
{
ReportTime = Environment.TickCount;
}
public bool GetScreen()
{
return Environment.TickCount - ScreenTime > Config.ScreenTime;
}
public void UpdateScreen()
{
ScreenTime = Environment.TickCount;
}
public bool Connected
{
get
{
return Connection != null && Connection.Connected == true;
}
}
[JsonIgnore]
public IConnection Connection { get; set; }
}
[MemoryPackable]
public sealed partial class SignInfo
{
public string MachineName { get; set; }
public string Version { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using common.libs;
using MemoryPack;
namespace cmonitor.server.service.messengers.sign
{
public sealed class SignInMessenger : IMessenger
{
private readonly SignCaching signCaching;
public SignInMessenger(SignCaching signCaching)
{
this.signCaching = signCaching;
}
[MessengerId((ushort)SignInMessengerIds.SignIn)]
public void SignIn(IConnection connection)
{
signCaching.Sign(connection, MemoryPackSerializer.Deserialize<SignInfo>(connection.ReceiveRequestWrap.Payload.Span));
connection.Write(Helper.TrueArray);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.sign
{
public enum SignInMessengerIds : ushort
{
SignIn = 0,
None = 99
}
}

View File

@@ -0,0 +1,22 @@
using cmonitor.server.client.reports.llock;
using MemoryPack;
namespace cmonitor.server.service.messengers.usb
{
public sealed class UsbMessenger : IMessenger
{
private readonly UsbReport usbReport;
public UsbMessenger(UsbReport usbReport)
{
this.usbReport = usbReport;
}
[MessengerId((ushort)UsbMessengerIds.Update)]
public void Update(IConnection connection)
{
usbReport.Update(MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span));
}
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.usb
{
public enum UsbMessengerIds : ushort
{
Update = 600,
None = 699
}
}

View File

@@ -0,0 +1,29 @@
using cmonitor.server.client.reports.volume;
using MemoryPack;
namespace cmonitor.server.service.messengers.volume
{
public sealed class VolumeMessenger : IMessenger
{
private readonly VolumeReport volumeReport;
public VolumeMessenger(VolumeReport volumeReport)
{
this.volumeReport = volumeReport;
}
[MessengerId((ushort)VolumeMessengerIds.Update)]
public void Update(IConnection connection)
{
float value = MemoryPackSerializer.Deserialize<float>(connection.ReceiveRequestWrap.Payload.Span);
volumeReport.SetVolume(value);
}
[MessengerId((ushort)VolumeMessengerIds.Mute)]
public void Mute(IConnection connection)
{
bool value = MemoryPackSerializer.Deserialize<bool>(connection.ReceiveRequestWrap.Payload.Span);
volumeReport.SetVolumeMute(value);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace cmonitor.server.service.messengers.volume
{
public enum VolumeMessengerIds : ushort
{
Update = 900,
Mute = 901,
None = 999
}
}

View File

@@ -0,0 +1,30 @@
using cmonitor.server.client.reports.llock;
using MemoryPack;
namespace cmonitor.server.service.messengers.wallpaper
{
public sealed class WallpaperMessenger : IMessenger
{
private readonly WallpaperReport wallpaperReport;
public WallpaperMessenger(WallpaperReport wallpaperReport)
{
this.wallpaperReport = wallpaperReport;
}
[MessengerId((ushort)WallpaperMessengerIds.Update)]
public void Update(IConnection connection)
{
WallpaperUpdateInfo wallpaperUpdateInfo = MemoryPackSerializer.Deserialize<WallpaperUpdateInfo>(connection.ReceiveRequestWrap.Payload.Span);
wallpaperReport.Update(wallpaperUpdateInfo.Value, wallpaperUpdateInfo.Url);
}
}
[MemoryPackable]
public sealed partial class WallpaperUpdateInfo
{
public bool Value { get; set; }
public string Url { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace cmonitor.server.service.messengers.wallpaper
{
public enum WallpaperMessengerIds : ushort
{
Update = 700,
None = 799
}
}

View File

@@ -0,0 +1,14 @@
namespace cmonitor.server.web
{
/// <summary>
/// web服务
/// </summary>
public interface IWebServer
{
/// <summary>
/// 开始
/// </summary>
public void Start();
}
}

View File

@@ -0,0 +1,100 @@
using common.libs;
using System.Net;
namespace cmonitor.server.web
{
/// <summary>
/// 本地web管理端服务器
/// </summary>
public sealed class WebServer : IWebServer
{
private readonly Config config;
public WebServer(Config config)
{
this.config = config;
}
/// <summary>
/// 开启web
/// </summary>
public void Start()
{
Task.Factory.StartNew(() =>
{
try
{
HttpListener http = new HttpListener();
http.Prefixes.Add($"http://+:{config.WebPort}/");
http.Start();
while (true)
{
HttpListenerContext context = http.GetContext();
HttpListenerRequest request = context.Request;
using HttpListenerResponse response = context.Response;
using Stream stream = response.OutputStream;
try
{
response.Headers.Set("Server", "snltty");
string path = request.Url.AbsolutePath;
//默认页面
if (path == "/") path = "index.html";
path = Path.Join(config.WebRoot, path);
if (File.Exists(path))
{
byte[] bytes = File.ReadAllBytes(path);
response.ContentLength64 = bytes.Length;
response.ContentType = GetContentType(path);
response.Headers.Set("Last-Modified", File.GetLastWriteTimeUtc(path).ToString());
stream.Write(bytes, 0, bytes.Length);
}
else
{
response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
catch (Exception)
{
response.StatusCode = (int)HttpStatusCode.BadRequest;
}
stream.Close();
stream.Dispose();
}
}
catch (Exception ex)
{
Logger.Instance.Error(ex);
}
}, TaskCreationOptions.LongRunning);
}
private Dictionary<string, string> types = new Dictionary<string, string> {
{ ".webp","image/webp"},
{ ".png","image/png"},
{ ".jpg","image/jpg"},
{ ".jpeg","image/jpeg"},
{ ".gif","image/gif"},
{ ".svg","image/svg+xml"},
{ ".ico","image/x-icon"},
{ ".js","text/javascript; charset=utf-8"},
{ ".html","text/html; charset=utf-8"},
{ ".css","text/css; charset=utf-8"},
{ ".pac","application/x-ns-proxy-autoconfig; charset=utf-8"},
};
private string GetContentType(string path)
{
string ext = Path.GetExtension(path);
if (types.ContainsKey(ext))
{
return types[ext];
}
return "application/octet-stream";
}
}
}

BIN
cmonitor/web/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
*{margin:0;padding:0;list-style:none}a{text-decoration:none;color:#6f9ccd}.flex{display:flex;display:-ms-flex;display:-o-flex;flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-1{flex:1 1 0%}.absolute{position:absolute;left:0;top:0;right:0;bottom:0}.relative{position:relative}.h-100{height:100%}.w-100{width:100%}.t-c{text-align:center}.t-r{text-align:right}.t-l{text-align:left!important}.m-r-1{margin-right:1rem}table{border-spacing:0;border-collapse:collapse}html{font-size:10px;background-color:#f4f4f4}body{overflow:hidden}span.split{width:.6rem}span.split-pad{padding:0 .3rem}span.split-pad10{padding:0 1rem}.middle{vertical-align:middle}.red{color:red}.scrollbar,.scrollbar-10,.scrollbar-4{overflow:auto}.scrollbar::-webkit-scrollbar{width:4px;height:1px}.scrollbar::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.scrollbar-4::-webkit-scrollbar{width:4px;height:4px}.scrollbar-4::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.scrollbar-10::-webkit-scrollbar{width:10px;height:1px}.scrollbar-10::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:10px}.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar{background:#f5f5f5}.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar-thumb{background:#ddd}.el-collapse-item__header{background-color:#fafafa!important;border-left:1px solid #ebeef5;border-right:1px solid #ebeef5;padding:0 2rem}.el-collapse-item__content{padding:1rem;border:1px solid #ebeef5;border-bottom:0}.el-input.w-search,.el-input.w-search .el-input__inner,.el-select.w-search{width:10rem}.el-form-item.w-search .el-form-item__label{font-size:1.2rem}.table-search .el-form--inline .el-form-item{margin-bottom:0}.el-dropdown,.el-dropdown-menu__item{font-size:1.3rem}.el-dropdown-menu__item a{color:#333}.el-input__inner:focus{border-color:var(--main-color)}.el-date-editor.el-input.w-auto,.el-date-editor.el-input__inner.w-auto{width:auto}.el-table .active-row{background:rgba(0,0,0,.15)}.el-table .table-green-row{background:rgba(0,255,0,.15)}.el-table .table-red-row{background:rgba(255,0,0,.15)}.el-table .table-green-row td,.el-table .table-red-row td{background:transparent!important}.el-date-editor.el-input,.el-date-editor.el-input__inner{width:auto}.el-table .active-row td{background:transparent!important}.el-table--border th{background-color:#fafafa}.el-table td,.el-table th.is-leaf,.el-table--border,.el-table--group,.el-table-filter{border-color:var(--main-border-color)}.el-pagination.is-background .el-pager li:not(.disabled).active{background-color:var(--main-color)}.el-pagination.is-background .el-pager li:not(.disabled):hover{color:var(--main-color)}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{width:inherit}.el-dialog{max-width:96%}.el-dialog__body .el-form-item:last-child{margin-bottom:0}.el-input-group__append,.el-input-group__prepend{padding:0 4px!important;background-color:transparent!important}.el-checkbox__label .el-icon{vertical-align:middle;margin-top:-2px}.el-color-picker{vertical-align:middle}.el-color-picker__trigger{border:0!important}.el-color-picker__color{border:0!important;border-radius:2px}.el-color-picker__color-inner{border-radius:2px}.el-message{min-width:10rem!important}.card-header{font-size:1.4rem}.forward-wrap .el-table--small.el-table .el-table__expanded-cell[class*=cell]{padding:20px 50px 20px 50px}h3.title{font-size:1.6rem;padding-bottom:.6rem;color:#555}.el-message-box{max-width:90%}.el-select-dropdown__item{padding-right:2rem!important}.el-form-item--default{--font-size:13px!important}.el-input__inner{font-size:13px}.el-dialog--center .el-dialog__body{padding-top:1rem!important;padding-bottom:1rem!important}

File diff suppressed because one or more lines are too long

BIN
cmonitor/web/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

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