mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-08 02:36:37 +08:00
feat(diagnosis): add protocol-aware connectivity diagnosis for tunnel chains
- Pass tunnel protocol through diagnosis work items - Support protocol-specific ping (TCP/UDP/KCP) via remote nodes - Add KCP probe support in websocket_reporter for chain hop testing
This commit is contained in:
@@ -42,6 +42,7 @@ type diagnosisWorkItem struct {
|
|||||||
toNode chainNodeRecord
|
toNode chainNodeRecord
|
||||||
hasChainHop bool
|
hasChainHop bool
|
||||||
ipPreference string
|
ipPreference string
|
||||||
|
protocol string
|
||||||
}
|
}
|
||||||
|
|
||||||
type diagnosisExecOptions struct {
|
type diagnosisExecOptions struct {
|
||||||
@@ -692,6 +693,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipPreference := h.repo.GetTunnelIPPreference(forward.TunnelID)
|
ipPreference := h.repo.GetTunnelIPPreference(forward.TunnelID)
|
||||||
|
protocol := strings.ToLower(strings.TrimSpace(tunnel.Protocol))
|
||||||
|
|
||||||
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
||||||
workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2+len(targets))
|
workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2+len(targets))
|
||||||
@@ -706,6 +708,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
targetIP: target.IP,
|
targetIP: target.IP,
|
||||||
targetPort: target.Port,
|
targetPort: target.Port,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
},
|
},
|
||||||
@@ -723,6 +726,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 2,
|
"toChainType": 2,
|
||||||
@@ -739,6 +743,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 3,
|
"toChainType": 3,
|
||||||
@@ -759,6 +764,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 2,
|
"fromChainType": 2,
|
||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
@@ -776,6 +782,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 2,
|
"fromChainType": 2,
|
||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
@@ -795,6 +802,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
targetIP: target.IP,
|
targetIP: target.IP,
|
||||||
targetPort: target.Port,
|
targetPort: target.Port,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 3,
|
"fromChainType": 3,
|
||||||
},
|
},
|
||||||
@@ -810,6 +818,7 @@ func (h *Handler) prepareForwardDiagnosis(forward *forwardRecord) (string, []dia
|
|||||||
targetIP: target.IP,
|
targetIP: target.IP,
|
||||||
targetPort: target.Port,
|
targetPort: target.Port,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
},
|
},
|
||||||
@@ -864,6 +873,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipPreference := h.repo.GetTunnelIPPreference(tunnelID)
|
ipPreference := h.repo.GetTunnelIPPreference(tunnelID)
|
||||||
|
protocol := strings.ToLower(strings.TrimSpace(tunnel.Protocol))
|
||||||
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
|
||||||
workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2)
|
workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2)
|
||||||
|
|
||||||
@@ -876,6 +886,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
targetIP: "www.bing.com",
|
targetIP: "www.bing.com",
|
||||||
targetPort: 443,
|
targetPort: 443,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
},
|
},
|
||||||
@@ -892,6 +903,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 2,
|
"toChainType": 2,
|
||||||
@@ -908,6 +920,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
"toChainType": 3,
|
"toChainType": 3,
|
||||||
@@ -928,6 +941,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 2,
|
"fromChainType": 2,
|
||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
@@ -945,6 +959,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
hasChainHop: true,
|
hasChainHop: true,
|
||||||
ipPreference: ipPreference,
|
ipPreference: ipPreference,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 2,
|
"fromChainType": 2,
|
||||||
"fromInx": currentNode.Inx,
|
"fromInx": currentNode.Inx,
|
||||||
@@ -963,6 +978,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
targetIP: "www.bing.com",
|
targetIP: "www.bing.com",
|
||||||
targetPort: 443,
|
targetPort: 443,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 3,
|
"fromChainType": 3,
|
||||||
},
|
},
|
||||||
@@ -976,6 +992,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
|
|||||||
targetIP: "www.bing.com",
|
targetIP: "www.bing.com",
|
||||||
targetPort: 443,
|
targetPort: 443,
|
||||||
description: description,
|
description: description,
|
||||||
|
protocol: protocol,
|
||||||
metadata: map[string]interface{}{
|
metadata: map[string]interface{}{
|
||||||
"fromChainType": 1,
|
"fromChainType": 1,
|
||||||
},
|
},
|
||||||
@@ -1095,9 +1112,9 @@ func (h *Handler) executeDiagnosisWorkItem(workItem diagnosisWorkItem, options d
|
|||||||
single := make([]map[string]interface{}, 0, 1)
|
single := make([]map[string]interface{}, 0, 1)
|
||||||
nodeCache := map[int64]*nodeRecord{}
|
nodeCache := map[int64]*nodeRecord{}
|
||||||
if workItem.hasChainHop {
|
if workItem.hasChainHop {
|
||||||
h.appendChainHopDiagnosis(&single, nodeCache, workItem.fromNodeID, workItem.toNode, workItem.description, workItem.metadata, workItem.ipPreference, options)
|
h.appendChainHopDiagnosis(&single, nodeCache, workItem.fromNodeID, workItem.toNode, workItem.description, workItem.metadata, workItem.ipPreference, workItem.protocol, options)
|
||||||
} else {
|
} else {
|
||||||
h.appendPathDiagnosis(&single, nodeCache, workItem.fromNodeID, workItem.targetIP, workItem.targetPort, workItem.description, workItem.metadata, options)
|
h.appendPathDiagnosis(&single, nodeCache, workItem.fromNodeID, workItem.targetIP, workItem.targetPort, workItem.description, workItem.metadata, workItem.protocol, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(single) == 0 {
|
if len(single) == 0 {
|
||||||
@@ -1223,14 +1240,14 @@ func (h *Handler) appendFailedDiagnosis(results *[]map[string]interface{}, nodeC
|
|||||||
item["nodeName"] = node.Name
|
item["nodeName"] = node.Name
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(message) == "" {
|
if strings.TrimSpace(message) == "" {
|
||||||
message = "TCP连接失败"
|
message = "连接失败"
|
||||||
}
|
}
|
||||||
item["success"] = false
|
item["success"] = false
|
||||||
item["message"] = message
|
item["message"] = message
|
||||||
*results = append(*results, item)
|
*results = append(*results, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) appendPathDiagnosis(results *[]map[string]interface{}, nodeCache map[int64]*nodeRecord, fromNodeID int64, targetIP string, targetPort int, description string, metadata map[string]interface{}, options diagnosisExecOptions) {
|
func (h *Handler) appendPathDiagnosis(results *[]map[string]interface{}, nodeCache map[int64]*nodeRecord, fromNodeID int64, targetIP string, targetPort int, description string, metadata map[string]interface{}, protocol string, options diagnosisExecOptions) {
|
||||||
item := newDiagnosisResultItem(fromNodeID, targetIP, targetPort, description, metadata)
|
item := newDiagnosisResultItem(fromNodeID, targetIP, targetPort, description, metadata)
|
||||||
|
|
||||||
fromNode, err := h.cachedNode(nodeCache, fromNodeID)
|
fromNode, err := h.cachedNode(nodeCache, fromNodeID)
|
||||||
@@ -1247,9 +1264,9 @@ func (h *Handler) appendPathDiagnosis(results *[]map[string]interface{}, nodeCac
|
|||||||
pingErr error
|
pingErr error
|
||||||
)
|
)
|
||||||
if fromNode.IsRemote == 1 {
|
if fromNode.IsRemote == 1 {
|
||||||
pingData, pingErr = h.tcpPingViaRemoteNode(fromNode, targetIP, targetPort, options)
|
pingData, pingErr = h.pingViaRemoteNode(fromNode, targetIP, targetPort, protocol, options)
|
||||||
} else {
|
} else {
|
||||||
pingData, pingErr = h.tcpPingViaNode(fromNodeID, targetIP, targetPort, options)
|
pingData, pingErr = h.pingViaNode(fromNodeID, targetIP, targetPort, protocol, options)
|
||||||
}
|
}
|
||||||
if pingErr != nil {
|
if pingErr != nil {
|
||||||
item["success"] = false
|
item["success"] = false
|
||||||
@@ -1266,21 +1283,21 @@ func (h *Handler) appendPathDiagnosis(results *[]map[string]interface{}, nodeCac
|
|||||||
message := strings.TrimSpace(asString(pingData["message"]))
|
message := strings.TrimSpace(asString(pingData["message"]))
|
||||||
if success {
|
if success {
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = "TCP连接成功"
|
message = "连接成功"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = strings.TrimSpace(asString(pingData["errorMessage"]))
|
message = strings.TrimSpace(asString(pingData["errorMessage"]))
|
||||||
}
|
}
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = "TCP连接失败"
|
message = "连接失败"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item["message"] = message
|
item["message"] = message
|
||||||
*results = append(*results, item)
|
*results = append(*results, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) appendChainHopDiagnosis(results *[]map[string]interface{}, nodeCache map[int64]*nodeRecord, fromNodeID int64, toNode chainNodeRecord, description string, metadata map[string]interface{}, ipPreference string, options diagnosisExecOptions) {
|
func (h *Handler) appendChainHopDiagnosis(results *[]map[string]interface{}, nodeCache map[int64]*nodeRecord, fromNodeID int64, toNode chainNodeRecord, description string, metadata map[string]interface{}, ipPreference string, protocol string, options diagnosisExecOptions) {
|
||||||
fromNode, _ := h.cachedNode(nodeCache, fromNodeID)
|
fromNode, _ := h.cachedNode(nodeCache, fromNodeID)
|
||||||
targetNode, err := h.cachedNode(nodeCache, toNode.NodeID)
|
targetNode, err := h.cachedNode(nodeCache, toNode.NodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1292,7 +1309,7 @@ func (h *Handler) appendChainHopDiagnosis(results *[]map[string]interface{}, nod
|
|||||||
h.appendFailedDiagnosis(results, nodeCache, fromNodeID, strings.Trim(strings.TrimSpace(targetNode.ServerIP), "[]"), toNode.Port, description, metadata, err.Error())
|
h.appendFailedDiagnosis(results, nodeCache, fromNodeID, strings.Trim(strings.TrimSpace(targetNode.ServerIP), "[]"), toNode.Port, description, metadata, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.appendPathDiagnosis(results, nodeCache, fromNodeID, targetIP, targetPort, description, metadata, options)
|
h.appendPathDiagnosis(results, nodeCache, fromNodeID, targetIP, targetPort, description, metadata, protocol, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveChainProbeTarget(fromNode, targetNode *nodeRecord, preferredPort int, ipPreference string, connectIp string) (string, int, error) {
|
func resolveChainProbeTarget(fromNode, targetNode *nodeRecord, preferredPort int, ipPreference string, connectIp string) (string, int, error) {
|
||||||
@@ -1392,6 +1409,44 @@ func (h *Handler) tcpPingViaRemoteNode(node *nodeRecord, ip string, port int, op
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUDPBasedProtocol(protocol string) bool {
|
||||||
|
p := strings.ToLower(strings.TrimSpace(protocol))
|
||||||
|
return p == "kcp" || p == "udp" || p == "quic"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) pingViaNode(nodeID int64, ip string, port int, protocol string, options diagnosisExecOptions) (map[string]interface{}, error) {
|
||||||
|
if isUDPBasedProtocol(protocol) {
|
||||||
|
return h.udpPingViaNode(nodeID, ip, port, options)
|
||||||
|
}
|
||||||
|
return h.tcpPingViaNode(nodeID, ip, port, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) pingViaRemoteNode(node *nodeRecord, ip string, port int, protocol string, options diagnosisExecOptions) (map[string]interface{}, error) {
|
||||||
|
return h.tcpPingViaRemoteNode(node, ip, port, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) udpPingViaNode(nodeID int64, ip string, port int, options diagnosisExecOptions) (map[string]interface{}, error) {
|
||||||
|
if options.commandTimeout <= 0 {
|
||||||
|
options.commandTimeout = diagnosisCommandTimeout
|
||||||
|
}
|
||||||
|
if options.pingTimeoutMS <= 0 {
|
||||||
|
options.pingTimeoutMS = int(diagnosisCommandTimeout / time.Millisecond)
|
||||||
|
}
|
||||||
|
res, err := h.sendNodeCommandWithTimeout(nodeID, "UdpPing", map[string]interface{}{
|
||||||
|
"ip": ip,
|
||||||
|
"port": port,
|
||||||
|
"count": 4,
|
||||||
|
"timeout": options.pingTimeoutMS,
|
||||||
|
}, options.commandTimeout, false, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.Data == nil {
|
||||||
|
return nil, errors.New("节点未返回诊断数据")
|
||||||
|
}
|
||||||
|
return res.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func splitRemoteTargets(remoteAddr string) []string {
|
func splitRemoteTargets(remoteAddr string) []string {
|
||||||
parts := strings.Split(remoteAddr, ",")
|
parts := strings.Split(remoteAddr, ",")
|
||||||
out := make([]string, 0, len(parts))
|
out := make([]string, 0, len(parts))
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ type TunnelRecord struct {
|
|||||||
Status int
|
Status int
|
||||||
Flow int64
|
Flow int64
|
||||||
TrafficRatio float64
|
TrafficRatio float64
|
||||||
|
Protocol string
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserQuotaView struct {
|
type UserQuotaView struct {
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ func (r *Repository) GetTunnelRecord(tunnelID int64) (*model.TunnelRecord, error
|
|||||||
Status: t.Status,
|
Status: t.Status,
|
||||||
Flow: t.Flow,
|
Flow: t.Flow,
|
||||||
TrafficRatio: t.TrafficRatio,
|
TrafficRatio: t.TrafficRatio,
|
||||||
|
Protocol: t.Protocol,
|
||||||
}
|
}
|
||||||
if tr.Flow <= 0 {
|
if tr.Flow <= 0 {
|
||||||
tr.Flow = 1
|
tr.Flow = 1
|
||||||
|
|||||||
@@ -845,6 +845,13 @@ func (w *WebSocketReporter) routeCommand(cmd CommandMessage) {
|
|||||||
response.Data = tcpPingResult
|
response.Data = tcpPingResult
|
||||||
// needSaveConfig = false (默认值)
|
// needSaveConfig = false (默认值)
|
||||||
|
|
||||||
|
// UDP Ping 诊断命令(只读,不需要保存配置)
|
||||||
|
case "UdpPing":
|
||||||
|
var udpPingResult TcpPingResponse
|
||||||
|
udpPingResult, err = w.handleUdpPing(cmd.Data)
|
||||||
|
response.Type = "UdpPingResponse"
|
||||||
|
response.Data = udpPingResult
|
||||||
|
|
||||||
// Service monitor check (read-only)
|
// Service monitor check (read-only)
|
||||||
case "ServiceMonitorCheck":
|
case "ServiceMonitorCheck":
|
||||||
var checkResult ServiceMonitorCheckResult
|
var checkResult ServiceMonitorCheckResult
|
||||||
@@ -1673,6 +1680,64 @@ func (w *WebSocketReporter) handleTcpPing(data interface{}) (TcpPingResponse, er
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WebSocketReporter) handleUdpPing(data interface{}) (TcpPingResponse, error) {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return TcpPingResponse{}, fmt.Errorf("序列化UDP ping数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var req TcpPingRequest
|
||||||
|
if err := json.Unmarshal(jsonData, &req); err != nil {
|
||||||
|
return TcpPingResponse{}, fmt.Errorf("解析UDP ping请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if net.ParseIP(req.IP) == nil && !isValidHostname(req.IP) {
|
||||||
|
return TcpPingResponse{
|
||||||
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
|
Success: false,
|
||||||
|
ErrorMessage: "无效的IP地址或主机名",
|
||||||
|
RequestId: req.RequestId,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Port <= 0 || req.Port > 65535 {
|
||||||
|
return TcpPingResponse{
|
||||||
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
|
Success: false,
|
||||||
|
ErrorMessage: "无效的端口号,范围应为1-65535",
|
||||||
|
RequestId: req.RequestId,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Count <= 0 {
|
||||||
|
req.Count = 4
|
||||||
|
}
|
||||||
|
if req.Timeout <= 0 {
|
||||||
|
req.Timeout = 5000
|
||||||
|
}
|
||||||
|
|
||||||
|
avgTime, packetLoss, err := udpPingHost(req.IP, req.Port, req.Count, req.Timeout)
|
||||||
|
|
||||||
|
response := TcpPingResponse{
|
||||||
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
|
RequestId: req.RequestId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.Success = false
|
||||||
|
response.ErrorMessage = err.Error()
|
||||||
|
} else {
|
||||||
|
response.Success = true
|
||||||
|
response.AverageTime = avgTime
|
||||||
|
response.PacketLoss = packetLoss
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleServiceMonitorCheck executes a service monitor check on this node.
|
// handleServiceMonitorCheck executes a service monitor check on this node.
|
||||||
// It always returns a result (command execution is considered successful even if the check fails).
|
// It always returns a result (command execution is considered successful even if the check fails).
|
||||||
func (w *WebSocketReporter) handleServiceMonitorCheck(data interface{}) (ServiceMonitorCheckResult, error) {
|
func (w *WebSocketReporter) handleServiceMonitorCheck(data interface{}) (ServiceMonitorCheckResult, error) {
|
||||||
@@ -1951,6 +2016,76 @@ func tcpPingHost(ip string, port int, count int, timeoutMs int) (float64, float6
|
|||||||
return avgTime, packetLoss, nil
|
return avgTime, packetLoss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func udpPingHost(ip string, port int, count int, timeoutMs int) (float64, float64, error) {
|
||||||
|
var totalTime float64
|
||||||
|
var successCount int
|
||||||
|
|
||||||
|
timeout := time.Duration(timeoutMs) * time.Millisecond
|
||||||
|
target := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||||
|
|
||||||
|
fmt.Printf("🔍 开始UDP ping测试: %s,次数: %d,超时: %dms\n", target, count, timeoutMs)
|
||||||
|
|
||||||
|
if net.ParseIP(ip) == nil {
|
||||||
|
fmt.Printf("🔍 检测到域名,正在解析DNS...\n")
|
||||||
|
dnsStart := time.Now()
|
||||||
|
|
||||||
|
addrs, err := net.LookupHost(ip)
|
||||||
|
dnsDuration := time.Since(dnsStart)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 100.0, fmt.Errorf("DNS解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return 0, 100.0, fmt.Errorf("DNS解析未返回任何IP地址")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ DNS解析完成 (%.2fms),解析到 %d 个IP: %v\n",
|
||||||
|
dnsDuration.Seconds()*1000, len(addrs), addrs)
|
||||||
|
|
||||||
|
target = net.JoinHostPort(addrs[0], fmt.Sprintf("%d", port))
|
||||||
|
fmt.Printf("🎯 使用IP地址进行测试: %s\n", target)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("🎯 使用IP地址进行测试: %s\n", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", target)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 100.0, fmt.Errorf("解析UDP地址失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout("udp", addr.String(), timeout)
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" 第%d次UDP连接失败: %v (%.2fms)\n", i+1, err, elapsed.Seconds()*1000)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" 第%d次UDP连接成功: %.2fms\n", i+1, elapsed.Seconds()*1000)
|
||||||
|
conn.Close()
|
||||||
|
totalTime += elapsed.Seconds() * 1000
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < count-1 {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if successCount == 0 {
|
||||||
|
return 0, 100.0, fmt.Errorf("所有UDP连接尝试都失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
avgTime := totalTime / float64(successCount)
|
||||||
|
packetLoss := float64(count-successCount) / float64(count) * 100
|
||||||
|
|
||||||
|
fmt.Printf("✅ UDP ping完成: 平均连接时间 %.2fms,失败率 %.1f%%\n", avgTime, packetLoss)
|
||||||
|
|
||||||
|
return avgTime, packetLoss, nil
|
||||||
|
}
|
||||||
|
|
||||||
// isValidHostname 验证主机名格式
|
// isValidHostname 验证主机名格式
|
||||||
func isValidHostname(hostname string) bool {
|
func isValidHostname(hostname string) bool {
|
||||||
if len(hostname) == 0 || len(hostname) > 253 {
|
if len(hostname) == 0 || len(hostname) > 253 {
|
||||||
|
|||||||
Reference in New Issue
Block a user