diff --git a/client/client.go b/client/client.go index 8946199d..73a790f5 100644 --- a/client/client.go +++ b/client/client.go @@ -242,7 +242,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { for i := range cli.servers { go func(index int) { defer wg.Done() - p, err := qualityTest(cli.servers[index]) + p, err := qualityTest(cli.servers[index].String()) if err != nil { pings[index] = 9999 return diff --git a/client/global.go b/client/global.go index 66024e8b..7427b028 100644 --- a/client/global.go +++ b/client/global.go @@ -488,10 +488,10 @@ func getSSOAddress() ([]*net.TCPAddr, error) { return adds, nil } -func qualityTest(addr *net.TCPAddr) (int64, error) { +func qualityTest(addr string) (int64, error) { // see QualityTestManager start := time.Now() - conn, err := net.DialTimeout("tcp", addr.String(), time.Second*5) + conn, err := net.DialTimeout("tcp", addr, time.Second*5) if err != nil { return 0, errors.Wrap(err, "failed to connect to server during quality test") } diff --git a/client/network.go b/client/network.go index dbb21768..66a2f4ec 100644 --- a/client/network.go +++ b/client/network.go @@ -1,7 +1,10 @@ package client import ( + "net" "runtime/debug" + "strings" + "sync" "sync/atomic" "time" @@ -12,6 +15,74 @@ import ( "github.com/Mrs4s/MiraiGo/utils" ) +// ConnectionQualityInfo 客户端连接质量测试结果 +// 延迟单位为 ms 如为 9999 则测试失败 测试方法为 TCP 连接测试 +// 丢包测试方法为 ICMP. 总共发送 10 个包, 记录丢包数 +type ConnectionQualityInfo struct { + // ChatServerLatency 聊天服务器延迟 + ChatServerLatency int64 + // ChatServerPacketLoss 聊天服务器ICMP丢包数 + ChatServerPacketLoss int + // LongMessageServerLatency 长消息服务器延迟. 涉及长消息以及合并转发消息下载 + LongMessageServerLatency int64 + // LongMessageServerResponseLatency 长消息服务器返回延迟 + LongMessageServerResponseLatency int64 + // SrvServerLatency Highway服务器延迟. 涉及媒体以及群文件上传 + SrvServerLatency int64 + // SrvServerPacketLoss Highway服务器ICMP丢包数. + SrvServerPacketLoss int +} + +func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo { + if !c.Online { + return nil + } + r := &ConnectionQualityInfo{} + wg := sync.WaitGroup{} + wg.Add(2) + go func(w *sync.WaitGroup) { + var err error + + if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil { + c.Error("test chat server latency error: %v", err) + r.ChatServerLatency = 9999 + } + + if addr, err := net.ResolveIPAddr("ip", "ssl.htdata.qq.com"); err == nil { + if r.LongMessageServerLatency, err = qualityTest((&net.TCPAddr{IP: addr.IP, Port: 443}).String()); err != nil { + c.Error("test long message server latency error: %v", err) + r.LongMessageServerLatency = 9999 + } + } else { + c.Error("resolve long message server error: %v", err) + r.LongMessageServerLatency = 9999 + } + + if r.SrvServerLatency, err = qualityTest(c.srvSsoAddrs[0]); err != nil { + c.Error("test srv server latency error: %v", err) + r.SrvServerLatency = 9999 + } + + w.Done() + }(&wg) + go func(w *sync.WaitGroup) { + res := utils.RunICMPPingLoop(&net.IPAddr{IP: c.servers[c.currServerIndex].IP}, 10) + r.ChatServerPacketLoss = res.PacketsLoss + res = utils.RunICMPPingLoop(&net.IPAddr{IP: net.ParseIP(strings.Split(c.srvSsoAddrs[0], ":")[0])}, 10) + r.SrvServerPacketLoss = res.PacketsLoss + w.Done() + }(&wg) + start := time.Now() + if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil { + r.LongMessageServerResponseLatency = time.Now().Sub(start).Milliseconds() + } else { + c.Error("test long message server response latency error: %v", err) + r.LongMessageServerResponseLatency = 9999 + } + wg.Wait() + return r +} + // connect 连接到 QQClient.servers 中的服务器 func (c *QQClient) connect() error { c.Info("connect to server: %v", c.servers[c.currServerIndex].String()) diff --git a/go.mod b/go.mod index 234d380e..efea79ad 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,10 @@ module github.com/Mrs4s/MiraiGo go 1.16 require ( - github.com/golang/protobuf v1.5.2 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.3.0 github.com/tidwall/gjson v1.8.1 + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/protobuf v1.27.1 ) diff --git a/go.sum b/go.sum index 97c6c9b6..782d4e48 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -57,7 +59,13 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/utils/icmp.go b/utils/icmp.go new file mode 100644 index 00000000..3a44eb16 --- /dev/null +++ b/utils/icmp.go @@ -0,0 +1,72 @@ +package utils + +import ( + "github.com/pkg/errors" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "math/rand" + "net" + "time" +) + +type ICMPPingResult struct { + PacketsSent int + PacketsRecv int + PacketsLoss int + Rtts []int64 +} + +func RunICMPPingLoop(addr *net.IPAddr, count int) *ICMPPingResult { + if count <= 0 { + return nil + } + r := &ICMPPingResult{ + PacketsSent: count, + Rtts: make([]int64, count), + } + for i := 1; i <= count; i++ { + rtt, err := SendICMPRequest(addr, i) + if err != nil { + r.PacketsLoss++ + r.Rtts[i-1] = 9999 + continue + } + r.PacketsRecv++ + r.Rtts[i-1] = rtt + time.Sleep(time.Millisecond * 100) + } + return r +} + +func SendICMPRequest(addr *net.IPAddr, seq int) (int64, error) { + data := make([]byte, 32) + rand.Read(data) + body := &icmp.Echo{ + ID: 0, + Seq: seq, + Data: data, + } + msg := &icmp.Message{ + Type: ipv4.ICMPTypeEcho, + Code: 0, + Body: body, + } + msgBytes, _ := msg.Marshal(nil) + conn, err := net.DialIP("ip4:icmp", nil, addr) + if err != nil { + return 0, errors.Wrap(err, "dial icmp conn error") + } + defer func() { _ = conn.Close() }() + if _, err = conn.Write(msgBytes); err != nil { + return 0, errors.Wrap(err, "write icmp packet error") + } + start := time.Now() + _ = conn.SetReadDeadline(time.Now().Add(time.Second * 2)) + buff := make([]byte, 1024) + _, err = conn.Read(buff) + if err != nil { + return 0, errors.Wrap(err, "read icmp conn error") + } + duration := time.Now().Sub(start).Milliseconds() + return duration, nil +}