mosdns/tools/probe.go
dengxiongjian cd761e8145
Some checks are pending
Test mosdns / build (push) Waiting to run
新增Mikrotik API 插入解析ip
2025-07-31 11:28:55 +08:00

239 lines
5.9 KiB
Go

/*
* Copyright (C) 2020-2022, IrineSistiana
*
* This file is part of mosdns.
*
* mosdns is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* mosdns is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tools
import (
"crypto/rand"
"crypto/tls"
"fmt"
"net"
"strconv"
"time"
"github.com/IrineSistiana/mosdns/v5/mlog"
"github.com/IrineSistiana/mosdns/v5/pkg/utils"
"github.com/miekg/dns"
"github.com/spf13/cobra"
)
func newIdleTimeoutCmd() *cobra.Command {
return &cobra.Command{
Use: "idle-timeout {tcp|tls}://server_addr[:port]",
Args: cobra.ExactArgs(1),
Short: "Probe server's idle timeout.",
Run: func(cmd *cobra.Command, args []string) {
if err := ProbServerTimeout(args[0]); err != nil {
mlog.S().Fatal(err)
}
},
DisableFlagsInUseLine: true,
}
}
func newConnReuseCmd() *cobra.Command {
return &cobra.Command{
Use: "conn-reuse {tcp|tls}://server_addr[:port]",
Args: cobra.ExactArgs(1),
Short: "Check whether this server supports RFC 1035 connection reuse.",
Run: func(cmd *cobra.Command, args []string) {
if err := ProbServerConnectionReuse(args[0]); err != nil {
mlog.S().Fatal(err)
}
},
DisableFlagsInUseLine: true,
}
}
func newPipelineCmd() *cobra.Command {
return &cobra.Command{
Use: "pipeline {tcp|tls}://server_addr[:port]",
Args: cobra.ExactArgs(1),
Short: "Check whether this server supports RFC 7766 query pipelining.",
Run: func(cmd *cobra.Command, args []string) {
if err := ProbServerPipeline(args[0]); err != nil {
mlog.S().Fatal(err)
}
},
DisableFlagsInUseLine: true,
}
}
func getConn(addr string) (net.Conn, error) {
tryAddPort := func(addr string, defaultPort int) string {
_, _, err := net.SplitHostPort(addr)
if err != nil { // no port, add it.
return net.JoinHostPort(addr, strconv.Itoa(defaultPort))
}
return addr
}
protocol, host := utils.SplitSchemeAndHost(addr)
if len(protocol) == 0 || len(host) == 0 {
return nil, fmt.Errorf("invalid addr %s", addr)
}
switch protocol {
case "tcp":
host = tryAddPort(host, 53)
return net.Dial("tcp", host)
case "tls":
host = tryAddPort(host, 853)
serverName, _, _ := net.SplitHostPort(host)
tlsConfig := new(tls.Config)
tlsConfig.InsecureSkipVerify = false
tlsConfig.ServerName = serverName
conn, err := net.Dial("tcp", host)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, tlsConfig)
tlsConn.SetDeadline(time.Now().Add(time.Second * 5))
err = tlsConn.Handshake()
if err != nil {
conn.Close()
return nil, fmt.Errorf("tls handshake failed: %v", err)
}
tlsConn.SetDeadline(time.Time{})
return tlsConn, nil
default:
return nil, fmt.Errorf("invalid protocol %s", protocol)
}
}
func ProbServerConnectionReuse(addr string) error {
c, err := getConn(addr)
if err != nil {
return err
}
defer c.Close()
conn := dns.Conn{Conn: c}
for i := 0; i < 3; i++ {
conn.SetDeadline(time.Now().Add(time.Second * 3))
q := new(dns.Msg)
q.SetQuestion("www.cloudflare.com.", dns.TypeA)
q.Id = uint16(i)
mlog.S().Infof("sending msg #%d", i)
err = conn.WriteMsg(q)
if err != nil {
return fmt.Errorf("failed to write #%d probe msg: %v", i, err)
}
_, err = conn.ReadMsg()
if err != nil {
return fmt.Errorf("failed to read #%d probe msg response: %v", i, err)
}
mlog.S().Infof("recevied response #%d", i)
}
mlog.S().Infof("server %s supports RFC 1035 connection reuse", addr)
return nil
}
func ProbServerPipeline(addr string) error {
c, err := getConn(addr)
if err != nil {
return err
}
defer c.Close()
conn := dns.Conn{Conn: c}
if err != nil {
return err
}
defer conn.Close()
domains := make([]string, 0)
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return err
}
domains = append(domains, fmt.Sprintf("%x.com.", b))
domains = append(domains, ".")
for i, d := range domains {
conn.SetDeadline(time.Now().Add(time.Second * 3))
q := new(dns.Msg)
q.SetQuestion(d, dns.TypeNS)
q.Id = uint16(i)
err = conn.WriteMsg(q)
if err != nil {
return fmt.Errorf("failed to write #%d probe msg: %v", i, err)
}
}
oooPassed := false
start := time.Now()
for i := range domains {
conn.SetDeadline(time.Now().Add(time.Second * 10))
m, err := conn.ReadMsg()
if err != nil {
return fmt.Errorf("failed to read #%d probe msg response: %v", i, err)
}
mlog.S().Infof("#%d response received, latency: %d ms", m.Id, time.Since(start).Milliseconds())
if m.Id != uint16(i) {
oooPassed = true
}
}
if oooPassed {
mlog.S().Info("server supports RFC7766 query pipelining")
} else {
mlog.S().Info("no out-of-order response received in this test, server MAY NOT support RFC7766 query pipelining")
}
return nil
}
func ProbServerTimeout(addr string) error {
c, err := getConn(addr)
if err != nil {
return err
}
defer c.Close()
conn := dns.Conn{Conn: c}
q := new(dns.Msg)
q.SetQuestion("www.cloudflare.com.", dns.TypeA)
err = conn.WriteMsg(q)
if err != nil {
return fmt.Errorf("failed to write probe msg: %v", err)
}
mlog.S().Info("testing server idle timeout, awaiting server closing the connection, this may take a while")
start := time.Now()
_, err = conn.ReadMsg()
if err != nil {
return fmt.Errorf("failed to read probe msg response: %v", err)
}
for {
_, err := conn.ReadMsg()
if err != nil {
break
}
}
mlog.S().Infof("connection closed by peer, it's idle timeout is %.2f sec", time.Since(start).Seconds())
return nil
}