feat: 完善堡垒机与 Agent 智能运维能力

- 支持 Windows Agent 构建、安装脚本和统一上报协议

- 增强 RDP 剪贴板、虚拟盘和文件传输能力

- 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口

- 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
Logan.Li committed 2026-07-05 23:49:36 +08:00
1 parent a9513c2056
commit e0d652d211
169 files changed
+12192 -9116

No files matched your search

+84 -26
View File
@@ -37,6 +37,17 @@ const (
gccSCSecurity = 0x0C02
gccSCNet = 0x0C03
// 静态虚拟通道声明(MS-RDPBCGR 2.2.7.1.1 Channel Pool)
// channel name 固定 8 字节 ASCII,不足补 0;options 控制通道加密/压缩行为 // Logan.Li
channelNameRdpdr = "rdpdr"
channelNameRdpsnd = "rdpsnd"
channelNameCliprdr = "cliprdr"
channelNameDrdynvc = "drdynvc"
channelOptionInitialized = 0x80000000
channelOptionEncryptRDP = 0x40000000
channelOptionShowProtocol = 0x00200000
channelOptionCompressRDP = 0x00800000
secFlagInfoPkt = 0x0040
secFlagLicensePkt = 0x0080
@@ -65,11 +76,15 @@ var errFinalizeActive = errors.New("RDP 会话已进入图形更新流")
// MCSProbeInfo 记录 NLA 后 MCS/GCC 连接期响应,用于确认图形会话层入口是否跑通 // Logan.Li
type MCSProbeInfo struct {
HandshakeInfo
UserDataBytes int
ServerCoreVersion uint32
ServerChannelID uint16
ServerChannelCount uint16
ServerChannelIDs []uint16
UserDataBytes int
ServerCoreVersion uint32
ServerChannelID uint16
ServerChannelCount uint16
ServerChannelIDs []uint16
// ChannelNameByID 记录服务端在 Connect-Response 里分配的 channel ID 对应的名称。
// 客户端在 Connect-Initial 里声明的 channel def 顺序与服务端返回的 ServerChannelIDs 一一对应,
// 据此建立 ID→名称映射,供 readLoop 按通道分发虚拟通道数据(cliprdr/rdpdr)。// Logan.Li
ChannelNameByID map[uint16]string
ServerEncryption uint32
ServerEncryptionLvl uint32
UserChannelID uint16
@@ -179,7 +194,7 @@ func probeMCSConnectOnce(ctx context.Context, cfg Config, address string, opts c
mcsInfo.DemandCapabilities = demand.capabilityCount
mcsInfo.DemandSource = string(demand.sourceDescriptor)
rdpDebugf(cfg, "demand active shared=0x%08x caps=%d source=%q firstSecurity=0x%04x firstPayload=%d", demand.sharedID, demand.capabilityCount, string(demand.sourceDescriptor), first.securityFlags, len(first.payload))
postTypes, err := sendConfirmActiveAndFinalize(tlsConn, cfg, userID, demand)
postTypes, _, err := sendConfirmActiveAndFinalize(tlsConn, cfg, userID, demand)
if err != nil {
return MCSProbeInfo{}, fmt.Errorf("Client Finalize 失败: shared=0x%08x caps=%d source=%q: %w", demand.sharedID, demand.capabilityCount, string(demand.sourceDescriptor), err)
}
@@ -258,7 +273,7 @@ func sendClientInfoAndReadDemand(rw io.ReadWriter, cfg Config, userID uint16) (m
return first, demand, nil
}
func sendConfirmActiveAndFinalize(rw io.ReadWriter, cfg Config, userID uint16, demand demandActiveProbe) ([]uint8, error) {
func sendConfirmActiveAndFinalize(rw io.ReadWriter, cfg Config, userID uint16, demand demandActiveProbe) ([]uint8, []mcsDataIndication, error) {
rdpDebugf(cfg, "finalize start userID=%d shared=0x%08x", userID, demand.sharedID)
pdus := []struct {
name string
@@ -274,22 +289,22 @@ func sendConfirmActiveAndFinalize(rw io.ReadWriter, cfg Config, userID uint16, d
for _, pdu := range pdus {
rdpDebugf(cfg, "finalize send %s bytes=%d", pdu.name, len(pdu.data))
if err := transport.WriteX224Data(rw, buildMCSSendDataRequest(userID, mcsGlobalChannelID, pdu.data)); err != nil {
return nil, fmt.Errorf("发送 %s 失败: %w", pdu.name, err)
return nil, nil, fmt.Errorf("发送 %s 失败: %w", pdu.name, err)
}
if isRDPStepMode(cfg) {
if stepTypes, err := readFinalizeStep(rw, pdu.name, 500*time.Millisecond); err != nil {
return append(types, stepTypes...), err
return append(types, stepTypes...), nil, err
} else if len(stepTypes) > 0 {
types = append(types, stepTypes...)
}
}
}
readTypes, err := waitFinalizeResponses(rw, cfg, 5*time.Second)
readTypes, pending, err := waitFinalizeResponses(rw, cfg, 5*time.Second)
if err != nil {
return types, err
return types, pending, err
}
types = append(types, readTypes...)
return types, nil
return types, pending, nil
}
func isRDPStepMode(cfg Config) bool {
@@ -321,7 +336,7 @@ func readFinalizeStep(rw io.ReadWriter, name string, timeout time.Duration) ([]u
return []uint8{got.type2}, nil
}
func waitFinalizeResponses(rw io.ReadWriter, cfg Config, timeout time.Duration) ([]uint8, error) {
func waitFinalizeResponses(rw io.ReadWriter, cfg Config, timeout time.Duration) ([]uint8, []mcsDataIndication, error) {
if conn, ok := rw.(interface{ SetReadDeadline(time.Time) error }); ok {
_ = conn.SetReadDeadline(time.Now().Add(timeout))
defer conn.SetReadDeadline(time.Time{})
@@ -333,28 +348,36 @@ func waitFinalizeResponses(rw io.ReadWriter, cfg Config, timeout time.Duration)
{type2: pduType2FontMap},
}
var types []uint8
var pending []mcsDataIndication
for _, want := range expected {
for {
payload, err := readNextSlowPathPayload(rw, cfg, timeout)
if err != nil {
if errors.Is(err, errFinalizeActive) || len(types) > 0 {
rdpDebugf(cfg, "finalize accept active session types=%v err=%v", types, err)
return types, nil
return types, pending, nil
}
return types, fmt.Errorf("读取 Client Finalize 响应失败: %w", err)
return types, pending, fmt.Errorf("读取 Client Finalize 响应失败: %w", err)
}
data, err := parseMCSDataIndication(payload)
if err != nil {
return types, err
return types, pending, err
}
// 虚拟通道数据(cliprdr/rdpdr 等,channel ≠ 1003 global)在 finalize 阶段
// 先缓存,等 SessionEngine 创建对应 handler 后再分发。 // Logan.Li
if data.channelID != mcsGlobalChannelID {
rdpDebugf(cfg, "finalize skip virtual channel data channel=%d payload=%d", data.channelID, len(data.payload))
pending = append(pending, data)
continue
}
got, err := parseShareDataPDUInfo(data.payload)
if err != nil {
return types, err
return types, pending, err
}
types = append(types, got.type2)
rdpDebugf(cfg, "finalize recv type2=0x%02x action=0x%04x raw=%s", got.type2, got.controlAction, got.rawPrefix)
if got.type2 == pduType2SetErrorInfo {
return types, fmt.Errorf("服务端返回 ErrorInfo: code=0x%08x(%s) seen=%v raw=%s", got.errorInfo, rdpErrorInfoName(got.errorInfo), types, got.rawPrefix)
return types, pending, fmt.Errorf("服务端返回 ErrorInfo: code=0x%08x(%s) seen=%v raw=%s", got.errorInfo, rdpErrorInfoName(got.errorInfo), types, got.rawPrefix)
}
if got.type2 == pduType2SaveSessionInfo {
continue
@@ -368,7 +391,7 @@ func waitFinalizeResponses(rw io.ReadWriter, cfg Config, timeout time.Duration)
break
}
}
return types, nil
return types, pending, nil
}
func readNextSlowPathPayload(r io.Reader, cfg Config, timeout time.Duration) ([]byte, error) {
@@ -492,13 +515,13 @@ func buildConfirmCapabilities(cfg Config) ([]byte, uint16) {
writeCapability(&out, capType, body)
count++
}
if cfg.MCSMode == "grdp-comment" || cfg.MCSMode == "step-grdp-comment" {
if cfg.MCSMode == "comment" || cfg.MCSMode == "step-comment" || cfg.MCSMode == "grdp-comment" || cfg.MCSMode == "step-grdp-comment" {
return buildGRDPCommentCapabilities(cfg, width, height)
}
addCapability(0x0001, buildGeneralCapability())
bitmap := bytes.Buffer{}
preferredBPP := uint16(24)
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "modern" || cfg.MCSMode == "step-modern" || cfg.MCSMode == "raw" || cfg.MCSMode == "step-raw" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
preferredBPP = 32
}
writeLE16(&bitmap, preferredBPP)
@@ -509,7 +532,7 @@ func buildConfirmCapabilities(cfg Config) ([]byte, uint16) {
writeLE16(&bitmap, uint16(height))
writeLE16(&bitmap, 0)
writeLE16(&bitmap, 1)
if cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
if cfg.MCSMode == "raw" || cfg.MCSMode == "step-raw" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
writeLE16(&bitmap, 0)
} else {
writeLE16(&bitmap, 1)
@@ -544,7 +567,7 @@ func buildConfirmCapabilities(cfg Config) ([]byte, uint16) {
addCapability(0x000e, mustHex("01000000"))
return out.Bytes(), count
}
if cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
if cfg.MCSMode == "modern" || cfg.MCSMode == "step-modern" || cfg.MCSMode == "raw" || cfg.MCSMode == "step-raw" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" || cfg.MCSMode == "grdp-raw" || cfg.MCSMode == "step-grdp-raw" {
addCapability(0x0003, buildGRDPOrderCapability())
addCapability(0x0005, mustHex("0000000002000200"))
addCapability(0x0007, mustHex("0000000000000000"))
@@ -575,7 +598,7 @@ func buildConfirmCapabilities(cfg Config) ([]byte, uint16) {
addCapability(0x0010, make([]byte, 48))
addCapability(0x0013, buildBitmapCacheRev2Capability())
addCapability(0x0011, mustHex("0000000000000000"))
addCapability(0x0014, mustHex("0000000000000000"))
addCapability(0x0014, mustHex("0000000040060000"))
addCapability(0x001a, mustHex("00000000"))
return out.Bytes(), count
}
@@ -887,6 +910,15 @@ func parseMCSChannelJoinConfirm(data []byte, expectedUserID uint16, expectedChan
return channelID, nil
}
// ParseMCSChannel 解析 MCS SendDataIndication,返回 channel ID(探针诊断用)。// Logan.Li
func ParseMCSChannel(data []byte) (uint16, error) {
ind, err := parseMCSDataIndication(data)
if err != nil {
return 0, err
}
return ind.channelID, nil
}
func parseMCSDataIndication(data []byte) (mcsDataIndication, error) {
r := bytes.NewReader(data)
pdu, err := readMCSPDUHeader(r)
@@ -1118,7 +1150,7 @@ func buildGCCClientCore(cfg Config) []byte {
}
highColorDepth := uint16(24)
supportedColorDepths := uint16(0x000f)
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" {
if cfg.MCSMode == "bpp32" || cfg.MCSMode == "step-bpp32" || cfg.MCSMode == "modern" || cfg.MCSMode == "step-modern" || cfg.MCSMode == "grdp-modern" || cfg.MCSMode == "step-grdp-modern" {
highColorDepth = 32
supportedColorDepths = 0x001f
}
@@ -1148,9 +1180,28 @@ func buildGCCClientCore(cfg Config) []byte {
return withGCCBlock(gccCSCore, body.Bytes())
}
// declaredStaticChannels 是客户端在 MCS Connect-Initial 里声明的静态虚拟通道。
// 顺序与服务端 Connect-Response 的 SCNet.ChannelIdArray 一一对应,保持接近 MSTSC/FreeRDP 的通道组合。// Logan.Li
var declaredStaticChannels = []struct {
Name string
Options uint32
}{
{Name: channelNameRdpdr, Options: channelOptionInitialized | channelOptionEncryptRDP},
{Name: channelNameRdpsnd, Options: channelOptionInitialized | channelOptionEncryptRDP},
{Name: channelNameCliprdr, Options: channelOptionInitialized | channelOptionEncryptRDP | channelOptionShowProtocol},
{Name: channelNameDrdynvc, Options: channelOptionInitialized | channelOptionCompressRDP},
}
func buildGCCClientNetwork() []byte {
body := bytes.Buffer{}
writeLE32(&body, 0)
writeLE32(&body, uint32(len(declaredStaticChannels))) // channelCount
for _, ch := range declaredStaticChannels {
// channel name: 8 字节 ASCII,不足补 0 // Logan.Li
nameBuf := make([]byte, 8)
copy(nameBuf, ch.Name)
body.Write(nameBuf)
writeLE32(&body, ch.Options)
}
return withGCCBlock(gccCSNet, body.Bytes())
}
@@ -1256,6 +1307,13 @@ func parseGCCServerBlock(blockType uint16, body []byte, info *MCSProbeInfo) {
for i := 0; i < int(info.ServerChannelCount) && r.Len() >= 2; i++ {
channelID, _ := readLE16(r)
info.ServerChannelIDs = append(info.ServerChannelIDs, channelID)
// 与 buildGCCClientNetwork 声明的 declaredStaticChannels 按顺序对应,建立 ID→名称映射。 // Logan.Li
if i < len(declaredStaticChannels) {
if info.ChannelNameByID == nil {
info.ChannelNameByID = make(map[uint16]string)
}
info.ChannelNameByID[channelID] = declaredStaticChannels[i].Name
}
}
case 0x0C04:
info.MessageChannelID, _ = readLE16(r)