feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
1 parent
a9513c2056
commit
e0d652d211
169 files changed
+12192
-9116
No files matched your search
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user