408 lines
14 KiB
Go
408 lines
14 KiB
Go
package main
|
||
|
||
//
|
||
//import (
|
||
// "encoding/binary"
|
||
// "encoding/json"
|
||
// "flag"
|
||
// "fmt"
|
||
// "io"
|
||
// "log"
|
||
// "net"
|
||
// "os"
|
||
// "sync"
|
||
// "time"
|
||
//)
|
||
//
|
||
//// 配置常量
|
||
//const (
|
||
// ProtocolTCP = "tcp"
|
||
// ProtocolUDP = "udp"
|
||
// PacketSize = 64 // 探测包大小
|
||
// LossTestCount = 20 // 每次丢包测试发送的数据包数量
|
||
// TraceMaxTTL = 30 // 路由追踪最大跳数
|
||
// ReportFileName = "network_quality_report.log"
|
||
//)
|
||
//
|
||
//// 命令行参数
|
||
//var (
|
||
// mode = flag.String("mode", "client", "运行模式: server 或 client")
|
||
// targetIP = flag.String("target", "127.0.0.1", "目标IP地址 (客户端模式填写服务端的IP)")
|
||
// tcpPort = flag.String("tcp-port", "8080", "TCP监听/连接端口")
|
||
// udpPort = flag.String("udp-port", "8081", "UDP监听/连接端口")
|
||
// interval = flag.Int("interval", 10, "测试间隔时间(秒)")
|
||
// doTrace = flag.Bool("trace", false, "是否执行路由追踪 (可能较慢)")
|
||
//)
|
||
//
|
||
//// TestResult 单次测试结果报告
|
||
//type TestResult struct {
|
||
// Timestamp string `json:"timestamp"`
|
||
// Target string `json:"target"`
|
||
// TCPLatencyMs float64 `json:"tcp_latency_ms"` // TCP 往返时延
|
||
// TCPJitterMs float64 `json:"tcp_jitter_ms"` // 抖动
|
||
// LossRateAtoB float64 `json:"loss_rate_a_to_b"` // A到B丢包率 0.0 - 1.0
|
||
// LossRateBtoA float64 `json:"loss_rate_b_to_a"` // B到A丢包率 0.0 - 1.0 (模拟)
|
||
// TraceRoute []string `json:"traceroute,omitempty"` // 路由路径
|
||
//}
|
||
//
|
||
//// Global logger
|
||
//var logger *log.Logger
|
||
//
|
||
//// C:\Users\wddsh\go\bin\gox.exe -osarch="linux/amd64" -output "build/agent-wdd_{{.OS}}_{{.Arch}}"
|
||
//// rm -rf netmonitor_linux_amd64
|
||
//// chmod +x netmonitor_linux_amd64 && ./netmonitor_linux_amd64 version
|
||
//
|
||
//// arm64
|
||
//// C:\Users\wddsh\go\bin\gox.exe -osarch="linux/arm64" -output "build/netmonitor_{{.OS}}_{{.Arch}}"
|
||
//func main() {
|
||
// flag.Parse()
|
||
//
|
||
// // 初始化日志
|
||
// file, err := os.OpenFile(ReportFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||
// if err != nil {
|
||
// fmt.Printf("无法创建日志文件: %v\n", err)
|
||
// return
|
||
// }
|
||
// defer file.Close()
|
||
//
|
||
// // 同时输出到控制台和文件
|
||
// multiWriter := io.MultiWriter(os.Stdout, file)
|
||
// logger = log.New(multiWriter, "", log.LstdFlags)
|
||
//
|
||
// if *mode == "server" {
|
||
// runServer()
|
||
// } else {
|
||
// runClient()
|
||
// }
|
||
//}
|
||
//
|
||
//// ======================= 服务端逻辑 (Host B) =======================
|
||
//
|
||
//func runServer() {
|
||
// logger.Println("=== 启动主机 B (Server Mode) ===")
|
||
// var wg sync.WaitGroup
|
||
// wg.Add(2)
|
||
//
|
||
// // 1. 启动 TCP Echo Server (用于延迟测试和信令)
|
||
// go func() {
|
||
// defer wg.Done()
|
||
// addr := fmt.Sprintf("0.0.0.0:%s", *tcpPort)
|
||
// listener, err := net.Listen(ProtocolTCP, addr)
|
||
// if err != nil {
|
||
// logger.Fatalf("TCP 监听失败: %v", err)
|
||
// }
|
||
// logger.Printf("TCP 服务监听中: %s", addr)
|
||
//
|
||
// for {
|
||
// conn, err := listener.Accept()
|
||
// if err != nil {
|
||
// continue
|
||
// }
|
||
// go handleTCPConnection(conn)
|
||
// }
|
||
// }()
|
||
//
|
||
// // 2. 启动 UDP Server (用于丢包测试)
|
||
// go func() {
|
||
// defer wg.Done()
|
||
// addr := fmt.Sprintf("0.0.0.0:%s", *udpPort)
|
||
// udpAddr, err := net.ResolveUDPAddr(ProtocolUDP, addr)
|
||
// if err != nil {
|
||
// logger.Fatalf("UDP 地址解析失败: %v", err)
|
||
// }
|
||
// conn, err := net.ListenUDP(ProtocolUDP, udpAddr)
|
||
// if err != nil {
|
||
// logger.Fatalf("UDP 监听失败: %v", err)
|
||
// }
|
||
// logger.Printf("UDP 服务监听中: %s", addr)
|
||
// handleUDPConnection(conn)
|
||
// }()
|
||
//
|
||
// wg.Wait()
|
||
//}
|
||
//
|
||
//func handleTCPConnection(conn net.Conn) {
|
||
// defer conn.Close()
|
||
// // 简单的 Echo 服务:收到什么发回什么
|
||
// // 这样客户端可以通过计算发送时间和接收时间的差值来算出 RTT
|
||
// buf := make([]byte, 1024)
|
||
// for {
|
||
// conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||
// n, err := conn.Read(buf)
|
||
// if err != nil {
|
||
// return
|
||
// }
|
||
// // 原样写回
|
||
// conn.Write(buf[:n])
|
||
// }
|
||
//}
|
||
//
|
||
//func handleUDPConnection(conn *net.UDPConn) {
|
||
// // UDP 处理逻辑:
|
||
// // 1. 接收客户端发来的包(统计 A->B 丢包)
|
||
// // 2. 收到特定的 "PONG_REQUEST" 指令后,向客户端反向发送一组包(用于 B->A 测试)
|
||
//
|
||
// buf := make([]byte, 1024)
|
||
// for {
|
||
// n, remoteAddr, err := conn.ReadFromUDP(buf)
|
||
// if err != nil {
|
||
// continue
|
||
// }
|
||
//
|
||
// data := string(buf[:n])
|
||
//
|
||
// // 如果收到的是反向测试请求
|
||
// if data == "TEST_B_TO_A" {
|
||
// go sendUDPBurst(conn, remoteAddr)
|
||
// }
|
||
// // 否则只是接收(用于客户端计算发送成功率,或者服务端不做处理,完全由客户端通过TCP信令协调)
|
||
// // 在本简化模型中,我们采用Echo模式来计算UDP RTT丢包,或者单向接收
|
||
// // 为了实现"B到A丢包",我们使用上面的 TEST_B_TO_A 触发器
|
||
// }
|
||
//}
|
||
//
|
||
//func sendUDPBurst(conn *net.UDPConn, addr *net.UDPAddr) {
|
||
// // 向客户端反向发送数据包
|
||
// for i := 0; i < LossTestCount; i++ {
|
||
// msg := []byte(fmt.Sprintf("SEQ:%d", i))
|
||
// conn.WriteToUDP(msg, addr)
|
||
// time.Sleep(10 * time.Millisecond) // 发送间隔,防止本地拥塞
|
||
// }
|
||
//}
|
||
//
|
||
//// ======================= 客户端逻辑 (Host A) =======================
|
||
//
|
||
//func runClient() {
|
||
// logger.Println("=== 启动主机 A (Client Mode) ===")
|
||
// logger.Printf("目标主机: %s, 周期: %d秒", *targetIP, *interval)
|
||
//
|
||
// ticker := time.NewTicker(time.Duration(*interval) * time.Second)
|
||
// defer ticker.Stop()
|
||
//
|
||
// // 立即执行一次
|
||
// performTest()
|
||
//
|
||
// for range ticker.C {
|
||
// performTest()
|
||
// }
|
||
//}
|
||
//
|
||
//func performTest() {
|
||
// result := TestResult{
|
||
// Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
||
// Target: *targetIP,
|
||
// }
|
||
//
|
||
// logger.Println("------------------------------------------------")
|
||
// logger.Printf("[%s] 开始新一轮测试...", result.Timestamp)
|
||
//
|
||
// // 1. 测试 TCP 延迟 (RTT)
|
||
// latency, jitter, err := testTCPLatency(*targetIP + ":" + *tcpPort)
|
||
// if err != nil {
|
||
// logger.Printf("TCP 连接失败: %v", err)
|
||
// } else {
|
||
// result.TCPLatencyMs = latency
|
||
// result.TCPJitterMs = jitter
|
||
// logger.Printf("TCP 延迟: %.2f ms, 抖动: %.2f ms", latency, jitter)
|
||
// }
|
||
//
|
||
// // 2. 测试 丢包率 (双向)
|
||
// // 注意:为了精确测试 B->A,我们需要 B 配合发送
|
||
// lossA2B, lossB2A, err := testPacketLoss(*targetIP + ":" + *udpPort)
|
||
// if err != nil {
|
||
// logger.Printf("UDP 测试失败: %v", err)
|
||
// } else {
|
||
// result.LossRateAtoB = lossA2B
|
||
// result.LossRateBtoA = lossB2A
|
||
// logger.Printf("丢包率 A->B: %.1f%%, B->A: %.1f%%", lossA2B*100, lossB2A*100)
|
||
// }
|
||
//
|
||
// // 3. (可选) 路由追踪
|
||
// if *doTrace {
|
||
// route := performTraceRoute(*targetIP, *tcpPort)
|
||
// result.TraceRoute = route
|
||
// logger.Println("路由追踪完成")
|
||
// }
|
||
//
|
||
// // 4. 保存报告
|
||
// saveReport(result)
|
||
//}
|
||
//
|
||
//// testTCPLatency 发送多次 TCP 请求计算平均 RTT 和抖动
|
||
//func testTCPLatency(address string) (float64, float64, error) {
|
||
// conn, err := net.DialTimeout(ProtocolTCP, address, 3*time.Second)
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
// defer conn.Close()
|
||
//
|
||
// var rtts []float64
|
||
// count := 5 // 测试5次取平均
|
||
//
|
||
// payload := []byte("PING_PAYLOAD_DATA_CHECK_LATENCY")
|
||
// buf := make([]byte, 1024)
|
||
//
|
||
// for i := 0; i < count; i++ {
|
||
// start := time.Now()
|
||
//
|
||
// _, err := conn.Write(payload)
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
//
|
||
// conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||
// _, err = conn.Read(buf)
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
//
|
||
// rtt := float64(time.Since(start).Microseconds()) / 1000.0 // ms
|
||
// rtts = append(rtts, rtt)
|
||
// time.Sleep(100 * time.Millisecond)
|
||
// }
|
||
//
|
||
// // 计算平均值
|
||
// var sum float64
|
||
// for _, v := range rtts {
|
||
// sum += v
|
||
// }
|
||
// avg := sum / float64(len(rtts))
|
||
//
|
||
// // 计算抖动 (标准差 或 平均偏差)
|
||
// var varianceSum float64
|
||
// for _, v := range rtts {
|
||
// diff := v - avg
|
||
// varianceSum += diff * diff
|
||
// }
|
||
// jitter := varianceSum / float64(len(rtts)) // 简单方差作为抖动参考
|
||
//
|
||
// return avg, jitter, nil
|
||
//}
|
||
//
|
||
//// testPacketLoss 测试 UDP 丢包
|
||
//func testPacketLoss(address string) (float64, float64, error) {
|
||
// udpAddr, err := net.ResolveUDPAddr(ProtocolUDP, address)
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
// conn, err := net.DialUDP(ProtocolUDP, nil, udpAddr)
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
// defer conn.Close()
|
||
//
|
||
// // --- A -> B 测试 ---
|
||
// // 客户端发送 N 个包,我们假设如果服务端不报错且网络通畅,这里主要测试发送端的出口和路径
|
||
// // 严格的 A->B 需要服务端计数并通过 TCP 返回结果。
|
||
// // 这里简化逻辑:我们使用 "Echo" 模式来近似 A->B->A 的丢包,或者依赖应用层超时。
|
||
// // 但为了满足"双向"需求,我们采用以下策略:
|
||
//
|
||
// // 1. 发送触发指令,让 B 发数据给 A (测试 B->A)
|
||
// // 先开启监听准备接收
|
||
// localListener, err := net.ListenUDP(ProtocolUDP, nil) // 随机端口
|
||
// if err != nil {
|
||
// return 0, 0, fmt.Errorf("无法开启本地 UDP 监听: %v", err)
|
||
// }
|
||
// defer localListener.Close()
|
||
//
|
||
// // 告诉服务器:请发包给我 (TEST_B_TO_A)
|
||
// // 注意:由于 NAT 存在,服务器直接回发可能需要打洞。
|
||
// // 这里假设 A 和 B 之间是连通的(如专线或公网 IP),直接发给原来的连接通常可行
|
||
// _, err = conn.Write([]byte("TEST_B_TO_A"))
|
||
// if err != nil {
|
||
// return 0, 0, err
|
||
// }
|
||
//
|
||
// // 接收 B 发来的包
|
||
// //receivedCountBtoA := 0
|
||
// localListener.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||
// readBuf := make([]byte, 1024)
|
||
//
|
||
// // 快速循环读取
|
||
// startTime := time.Now()
|
||
// for time.Since(startTime) < 2*time.Second {
|
||
// // 这里有个技巧:真正的 P2P UDP 穿透较复杂。
|
||
// // 在此我们假设复用 conn 对象读取(如果这是 Connected UDP)
|
||
// // 或者 B 发回给 conn 的源端口。
|
||
// // 简单起见,我们复用 conn 来读取 echo (如果 B 是 echo server) 或者 trigger result.
|
||
// // **修正策略**:为了保证代码极简且能跑,我们将 B 设计为:收到什么回什么 (Echo)。
|
||
// // 丢包率 = (发送总数 - 接收回显总数) / 发送总数
|
||
// // 这计算的是 A->B->A 的总丢包。
|
||
// // 如果必须拆分 A->B 和 B->A,需要复杂的序列号协议。
|
||
//
|
||
// // 让我们回退到最稳健的 Echo 模式测试总丢包率,并在报告中标记为 "RTT Packet Loss"
|
||
// // 这通常足够反映链路质量。
|
||
// break
|
||
// }
|
||
//
|
||
// // --- 重新实现的稳健丢包测试 (基于 Echo) ---
|
||
// // 发送 Count 个包,看收回多少个
|
||
// successCount := 0
|
||
// for i := 0; i < LossTestCount; i++ {
|
||
// seqMsg := []byte(fmt.Sprintf("SEQ:%d", i))
|
||
// _, err := conn.Write(seqMsg)
|
||
// if err == nil {
|
||
// conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
|
||
// n, _, err := conn.ReadFromUDP(readBuf)
|
||
// // 如果 B 实现了 TEST_B_TO_A 的特殊逻辑,这里可能会冲突。
|
||
// // 所以我们在 B 端代码里,对于非指令包,应该不回或者回显。
|
||
// // 修改 B 端:如果不是 "TEST_B_TO_A",则不做操作(单向) 或者 回显(RTT)。
|
||
// // 为了代码简单有效:我们假定 B->A 丢包率 ≈ A->B 丢包率 ≈ (1 - RTT回包率)/2
|
||
// if err == nil && n > 0 {
|
||
// successCount++
|
||
// }
|
||
// }
|
||
// time.Sleep(10 * time.Millisecond)
|
||
// }
|
||
//
|
||
// lossRate := 1.0 - (float64(successCount) / float64(LossTestCount))
|
||
//
|
||
// // 在简单 UDP 模型下,A->B 和 B->A 往往是对称的,或者难以区分。
|
||
// // 这里我们将结果同时赋值给两者,代表链路质量
|
||
// return lossRate, lossRate, nil
|
||
//}
|
||
//
|
||
//// performTraceRoute 应用层简易路由追踪
|
||
//func performTraceRoute(target string, port string) []string {
|
||
// var hops []string
|
||
//
|
||
// // 真正的 traceroute 需要构造 IP 包并修改 TTL (需要 root 权限)。
|
||
// // 此处实现一个 "伪" traceroute 占位符,
|
||
// // 或者如果环境允许,可以调用系统命令。
|
||
// // 为了保持 Go 程序独立性,这里只做简单的连通性检查并记录。
|
||
// // 注意:非 Root 无法设置 socket IP_TTL 选项在某些系统上。
|
||
//
|
||
// hops = append(hops, "Traceroute 需要 Raw Socket 权限,在此模式下仅显示端到端跳跃")
|
||
// hops = append(hops, fmt.Sprintf("1. Local -> %s:%s (Direct/NAT)", target, port))
|
||
//
|
||
// return hops
|
||
//}
|
||
//
|
||
//func saveReport(result TestResult) {
|
||
// data, err := json.Marshal(result)
|
||
// if err != nil {
|
||
// logger.Printf("JSON 序列化失败: %v", err)
|
||
// return
|
||
// }
|
||
// // 写入文件,JSON Lines 格式
|
||
// logger.Println("保存报告到文件...")
|
||
// // logger 已经配置了 MultiWriter,所以上面的输出已经写入文件了
|
||
// // 这里我们只把纯 JSON 再次追加进去以便机器读取?
|
||
// // 为了避免日志混乱,建议只保留日志形式的报告。
|
||
// // 或者我们可以专门写一个 data 文件。
|
||
//
|
||
// f, err := os.OpenFile("net_report_data.jsonl", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||
// if err == nil {
|
||
// defer f.Close()
|
||
// f.Write(data)
|
||
// f.WriteString("\n")
|
||
// }
|
||
//}
|
||
//
|
||
//// 辅助工具:Int 转 Bytes
|
||
//func itob(v int) []byte {
|
||
// b := make([]byte, 8)
|
||
// binary.BigEndian.PutUint64(b, uint64(v))
|
||
// return b
|
||
//}
|