mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
Merge pull request #1434 from bbx-winner/feat-dial-test
feat: support ping/dns/traceroute for dial test
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
- fix: optimize websocket step initialization
|
||||
- fix: reuse plugin instance if already initialized
|
||||
- fix: deep copy api step to avoid data racing
|
||||
- feat: support ping/dns/traceroute for dial test
|
||||
|
||||
## v4.1.6 (2022-07-04)
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -9,6 +9,7 @@ require (
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-openapi/spec v0.20.6
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
@@ -17,6 +18,7 @@ require (
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/maja42/goval v1.2.1
|
||||
github.com/miekg/dns v1.0.14
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
|
||||
6
go.sum
6
go.sum
@@ -145,6 +145,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
@@ -222,6 +224,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -343,6 +346,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -510,6 +514,7 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -615,6 +620,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
96
hrp/cmd/dial.go
Normal file
96
hrp/cmd/dial.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/dial"
|
||||
)
|
||||
|
||||
var (
|
||||
pingOptions dial.PingOptions
|
||||
dnsOptions dial.DnsOptions
|
||||
traceRouteOptions dial.TraceRouteOptions
|
||||
)
|
||||
|
||||
var pingCmd = &cobra.Command{
|
||||
Use: "ping $url",
|
||||
Short: "run integrated ping command",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dial.DoPing(&pingOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
var dnsCmd = &cobra.Command{
|
||||
Use: "dns $url",
|
||||
Short: "DNS resolution for different source and record types",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if dnsOptions.DnsSourceType != dial.DnsSourceTypeLocal && dnsOptions.DnsServer != "" {
|
||||
log.Warn().Msg("DNS server not supported for non-local DNS source, ignored")
|
||||
}
|
||||
if dnsOptions.DnsSourceType == dial.DnsSourceTypeHttp && dnsOptions.DnsRecordType == dial.DnsRecordTypeCNAME {
|
||||
log.Warn().Msg("CNAME record not supported for http DNS source, using default record type(A)")
|
||||
}
|
||||
return dial.DoDns(&dnsOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
var traceRouteCmd = &cobra.Command{
|
||||
Use: "traceroute $url",
|
||||
Short: "run integrated traceroute command",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
log.Info().Msg("using default probe number (3) on Windows")
|
||||
}
|
||||
return dial.DoTraceRoute(&traceRouteOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
var curlCmd = &cobra.Command{
|
||||
Use: "curl $url",
|
||||
Short: "run integrated curl command",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
DisableFlagParsing: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dial.DoCurl(args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(pingCmd)
|
||||
pingCmd.Flags().IntVarP(&pingOptions.Count, "count", "c", 10, "Stop after sending (and receiving) N packets")
|
||||
pingCmd.Flags().DurationVarP(&pingOptions.Timeout, "timeout", "t", 20*time.Second, "Ping exits after N seconds")
|
||||
pingCmd.Flags().DurationVarP(&pingOptions.Interval, "interval", "i", 1*time.Second, "Wait N seconds between sending each packet")
|
||||
pingCmd.Flags().BoolVar(&pingOptions.SaveTests, "save-tests", false, "Save ping result as json")
|
||||
|
||||
rootCmd.AddCommand(dnsCmd)
|
||||
dnsCmd.Flags().IntVar(&dnsOptions.DnsSourceType, "dns-source", 0, "DNS source type\n0: local DNS\n1: http DNS\n2: google DNS")
|
||||
dnsCmd.Flags().IntVar(&dnsOptions.DnsRecordType, "dns-record", 1, "DNS record type\n1: A\n28: AAAA\n5: CNAME")
|
||||
dnsCmd.Flags().StringVar(&dnsOptions.DnsServer, "dns-server", "", "DNS server, only available for local DNS source")
|
||||
dnsCmd.Flags().BoolVar(&dnsOptions.SaveTests, "save-tests", false, "Save DNS resolution result as json")
|
||||
|
||||
rootCmd.AddCommand(traceRouteCmd)
|
||||
traceRouteCmd.Flags().IntVarP(&traceRouteOptions.MaxTTL, "max-hops", "m", 30, "Set the max number of hops (max TTL to be reached)")
|
||||
traceRouteCmd.Flags().IntVarP(&traceRouteOptions.Queries, "queries", "q", 1, "Set the number of probes per each hop")
|
||||
traceRouteCmd.Flags().BoolVar(&traceRouteOptions.SaveTests, "save-tests", false, "Save traceroute result as json")
|
||||
|
||||
rootCmd.AddCommand(curlCmd)
|
||||
}
|
||||
72
hrp/internal/dial/curl.go
Normal file
72
hrp/internal/dial/curl.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package dial
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
const (
|
||||
normalResult = "STDOUT"
|
||||
errorResult = "STDERR"
|
||||
failedResult = "FAILED"
|
||||
)
|
||||
|
||||
type CurlResult struct {
|
||||
Result string `json:"result"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
ResultType string `json:"resultType"`
|
||||
}
|
||||
|
||||
func DoCurl(args []string) (err error) {
|
||||
var saveTests bool
|
||||
for i, arg := range args {
|
||||
if arg == "--save-tests" {
|
||||
args = append(args[:i], args[i+1:]...)
|
||||
saveTests = true
|
||||
}
|
||||
}
|
||||
var curlResult CurlResult
|
||||
defer func() {
|
||||
if saveTests {
|
||||
dir, _ := os.Getwd()
|
||||
curlResultName := fmt.Sprintf("curl_result_%v.json", time.Now().Format("20060102150405"))
|
||||
curlResultPath := filepath.Join(dir, curlResultName)
|
||||
err = builtin.Dump2JSON(curlResult, curlResultPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("save dns resolution result failed")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
cmd := exec.Command("curl", args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("fail to run curl command")
|
||||
curlResult.ErrorMsg = err.Error()
|
||||
curlResult.Result = stderr.String()
|
||||
curlResult.ResultType = errorResult
|
||||
return
|
||||
}
|
||||
if stdout.String() != "" {
|
||||
fmt.Printf(stdout.String())
|
||||
curlResult.Result = stdout.String()
|
||||
curlResult.ResultType = normalResult
|
||||
} else if stderr.String() != "" {
|
||||
fmt.Printf(stderr.String())
|
||||
curlResult.ErrorMsg = stderr.String()
|
||||
curlResult.ResultType = errorResult
|
||||
}
|
||||
return
|
||||
}
|
||||
251
hrp/internal/dial/dns.go
Normal file
251
hrp/internal/dial/dns.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package dial
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
const (
|
||||
httpDnsUrl = "https://dig.bdurl.net/q"
|
||||
googleDnsUrl = "https://dns.google/resolve"
|
||||
)
|
||||
|
||||
const (
|
||||
DnsSourceTypeLocal = iota
|
||||
DnsSourceTypeHttp
|
||||
DnsSourceTypeGoogle
|
||||
)
|
||||
|
||||
const (
|
||||
DnsRecordTypeA = 1
|
||||
DnsRecordTypeAAAA = 28
|
||||
DnsRecordTypeCNAME = 5
|
||||
)
|
||||
|
||||
var dnsHttpClient = &http.Client{
|
||||
Timeout: 5 * time.Minute,
|
||||
}
|
||||
|
||||
type DnsOptions struct {
|
||||
DnsSourceType int
|
||||
DnsRecordType int
|
||||
DnsServer string
|
||||
SaveTests bool
|
||||
}
|
||||
|
||||
type DnsResult struct {
|
||||
DnsList []string `json:"dnsList"`
|
||||
DnsSource int `json:"dnsType"`
|
||||
DnsRecordType int `json:"dnsRecordType"`
|
||||
DnsServer string `json:"dnsServer,omitempty"`
|
||||
Ttl int `json:"ttl"`
|
||||
Suc bool `json:"suc"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
}
|
||||
|
||||
type googleDnsResp struct {
|
||||
Answer []googleDnsAnswer `json:"Answer"`
|
||||
}
|
||||
|
||||
type httpDnsResp struct {
|
||||
Ips []string `json:"ips"`
|
||||
Ttl int `json:"ttl"`
|
||||
}
|
||||
|
||||
type googleDnsAnswer struct {
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
TTL int `json:"TTL"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
func ParseIP(s string) (net.IP, int) {
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
return nil, 0
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '.':
|
||||
return ip, 4
|
||||
case ':':
|
||||
return ip, 6
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func localDns(src string, dnsRecordType int, dnsServer string) (dnsResult DnsResult, err error) {
|
||||
dnsResult.DnsSource = DnsSourceTypeLocal
|
||||
dnsResult.DnsRecordType = dnsRecordType
|
||||
|
||||
if dnsServer == "" {
|
||||
config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
dnsServer = config.Servers[0]
|
||||
} else {
|
||||
dnsResult.DnsServer = dnsServer
|
||||
}
|
||||
|
||||
_, ipType := ParseIP(dnsServer)
|
||||
if ipType == 4 {
|
||||
dnsServer += ":53"
|
||||
}
|
||||
|
||||
c := dns.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
m := dns.Msg{}
|
||||
|
||||
m.SetQuestion(src+".", uint16(dnsRecordType))
|
||||
r, _, err := c.Exchange(&m, dnsServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, ans := range r.Answer {
|
||||
switch dnsRecordType {
|
||||
case DnsRecordTypeA:
|
||||
record, isType := ans.(*dns.A)
|
||||
if isType {
|
||||
dnsResult.Ttl = int(record.Hdr.Ttl)
|
||||
dnsResult.DnsList = append(dnsResult.DnsList, record.A.String())
|
||||
}
|
||||
case DnsRecordTypeAAAA:
|
||||
record, isType := ans.(*dns.AAAA)
|
||||
if isType {
|
||||
dnsResult.Ttl = int(record.Hdr.Ttl)
|
||||
dnsResult.DnsList = append(dnsResult.DnsList, record.AAAA.String())
|
||||
}
|
||||
case DnsRecordTypeCNAME:
|
||||
record, isType := ans.(*dns.CNAME)
|
||||
if isType {
|
||||
dnsResult.Ttl = int(record.Hdr.Ttl)
|
||||
dnsResult.DnsList = append(dnsResult.DnsList, record.Target)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func httpDns(url string, dnsRecordType int) (dnsResult DnsResult, err error) {
|
||||
target := httpDnsUrl + "?host=" + url
|
||||
if dnsRecordType == DnsRecordTypeAAAA {
|
||||
target += "&aid=13&f=2"
|
||||
}
|
||||
resp, err := dnsHttpClient.Get(target)
|
||||
|
||||
dnsResult.DnsSource = DnsSourceTypeHttp
|
||||
dnsResult.DnsRecordType = dnsRecordType
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var buf []byte
|
||||
buf, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var result httpDnsResp
|
||||
err = json.Unmarshal(buf, &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dnsResult.DnsList = result.Ips
|
||||
dnsResult.Ttl = result.Ttl
|
||||
return
|
||||
}
|
||||
|
||||
func googleDns(url string, dnsRecordType int) (dnsResult DnsResult, err error) {
|
||||
resp, err := dnsHttpClient.Get(googleDnsUrl + "?name=" + url + "&type=" + strconv.Itoa(dnsRecordType))
|
||||
|
||||
dnsResult.DnsSource = DnsSourceTypeGoogle
|
||||
dnsResult.DnsRecordType = dnsRecordType
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var buf []byte
|
||||
buf, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var result googleDnsResp
|
||||
err = json.Unmarshal(buf, &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(result.Answer) == 0 {
|
||||
return
|
||||
}
|
||||
for _, answer := range result.Answer {
|
||||
if answer.Type == dnsRecordType {
|
||||
dnsResult.Ttl = answer.TTL
|
||||
dnsResult.DnsList = append(dnsResult.DnsList, answer.Data)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DoDns(dnsOptions *DnsOptions, args []string) (err error) {
|
||||
if len(args) != 1 {
|
||||
return errors.New("there should be one argument")
|
||||
}
|
||||
|
||||
var dnsResult DnsResult
|
||||
defer func() {
|
||||
if dnsOptions.SaveTests {
|
||||
dir, _ := os.Getwd()
|
||||
dnsResultName := fmt.Sprintf("dns_result_%v.json", time.Now().Format("20060102150405"))
|
||||
dnsResultPath := filepath.Join(dir, dnsResultName)
|
||||
err = builtin.Dump2JSON(dnsResult, dnsResultPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("save dns resolution result failed")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dnsTarget := args[0]
|
||||
|
||||
parsedURL, err := url.Parse(dnsTarget)
|
||||
if err == nil && parsedURL.Host != "" {
|
||||
log.Info().Msgf("parse input url %v and extract host %v", dnsTarget, parsedURL.Host)
|
||||
dnsTarget = strings.Split(parsedURL.Host, ":")[0]
|
||||
}
|
||||
log.Info().Msgf("resolve DNS for %v", dnsTarget)
|
||||
dnsRecordType := dnsOptions.DnsRecordType
|
||||
dnsServer := dnsOptions.DnsServer
|
||||
switch dnsOptions.DnsSourceType {
|
||||
case DnsSourceTypeLocal:
|
||||
dnsResult, err = localDns(dnsTarget, dnsRecordType, dnsServer)
|
||||
case DnsSourceTypeHttp:
|
||||
dnsResult, err = httpDns(dnsTarget, dnsRecordType)
|
||||
case DnsSourceTypeGoogle:
|
||||
dnsResult, err = googleDns(dnsTarget, dnsRecordType)
|
||||
}
|
||||
if err != nil {
|
||||
dnsResult.Suc = false
|
||||
dnsResult.ErrMsg = err.Error()
|
||||
log.Error().Err(err).Msgf("fail to do DNS for %s", dnsTarget)
|
||||
} else {
|
||||
dnsResult.Suc = true
|
||||
dnsResult.ErrMsg = ""
|
||||
fmt.Printf("\nDNS resolution done, result IP list: %v\n", dnsResult.DnsList)
|
||||
}
|
||||
return
|
||||
}
|
||||
116
hrp/internal/dial/ping.go
Normal file
116
hrp/internal/dial/ping.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package dial
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
type PingOptions struct {
|
||||
Count int
|
||||
Timeout time.Duration
|
||||
Interval time.Duration
|
||||
SaveTests bool
|
||||
}
|
||||
|
||||
type PingResult struct {
|
||||
Suc bool `json:"suc"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
Ip string `json:"ip"`
|
||||
AvgCost int `json:"avgCost"`
|
||||
MaxCost int `json:"maxCost"`
|
||||
MinCost int `json:"minCost"`
|
||||
Lost int `json:"lost"`
|
||||
PingCount int `json:"pingCount"`
|
||||
PacketSize int `json:"packetSize"`
|
||||
ReceivePacketCount int `json:"receivePacketCount"`
|
||||
SendPacketCount int `json:"sendPacketCount"`
|
||||
SuccessCount int `json:"successCount"`
|
||||
DebugLog string `json:"debugLog"`
|
||||
}
|
||||
|
||||
func DoPing(pingOptions *PingOptions, args []string) (err error) {
|
||||
if len(args) != 1 {
|
||||
return errors.New("there should be one argument")
|
||||
}
|
||||
|
||||
var pingResult PingResult
|
||||
defer func() {
|
||||
if pingOptions.SaveTests {
|
||||
dir, _ := os.Getwd()
|
||||
pingResultName := fmt.Sprintf("ping_result_%v.json", time.Now().Format("20060102150405"))
|
||||
pingResultPath := filepath.Join(dir, pingResultName)
|
||||
err = builtin.Dump2JSON(pingResult, pingResultPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("save ping result failed")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
pingTarget := args[0]
|
||||
|
||||
parsedURL, err := url.Parse(pingTarget)
|
||||
if err == nil && parsedURL.Host != "" {
|
||||
log.Info().Msgf("parse input url %v and extract host %v", pingTarget, parsedURL.Host)
|
||||
pingTarget = strings.Split(parsedURL.Host, ":")[0]
|
||||
}
|
||||
|
||||
log.Info().Msgf("ping host %v", pingTarget)
|
||||
pinger, err := ping.NewPinger(pingTarget)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("fail to get pinger for %s", pingTarget)
|
||||
pingResult.Suc = false
|
||||
pingResult.ErrMsg = err.Error()
|
||||
pingResult.DebugLog = err.Error()
|
||||
return
|
||||
}
|
||||
pinger.Count = pingOptions.Count
|
||||
pinger.Timeout = pingOptions.Timeout
|
||||
pinger.Interval = pingOptions.Interval
|
||||
|
||||
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||
pingResult.DebugLog += fmt.Sprintf("%d bytes from %s: icmp_seq=%d time=%v\n",
|
||||
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||
}
|
||||
pinger.OnFinish = func(stats *ping.Statistics) {
|
||||
pingResult.DebugLog += fmt.Sprintf("\n--- %s ping statistics ---\n", stats.Addr)
|
||||
pingResult.DebugLog += fmt.Sprintf("%d packets transmitted, %d packets received, %v%% packet loss\n",
|
||||
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||
pingResult.DebugLog += fmt.Sprintf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
|
||||
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||
}
|
||||
pingResult.DebugLog += fmt.Sprintf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
|
||||
|
||||
err = pinger.Run() // blocks until finished
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("fail to run ping for %s", parsedURL)
|
||||
pingResult.Suc = false
|
||||
pingResult.ErrMsg = err.Error()
|
||||
pingResult.DebugLog = err.Error()
|
||||
return
|
||||
}
|
||||
fmt.Print(pingResult.DebugLog)
|
||||
stats := pinger.Statistics() // get send/receive/rtt stats
|
||||
pingResult.Ip = pinger.IPAddr().String()
|
||||
pingResult.AvgCost = int(stats.AvgRtt / time.Millisecond)
|
||||
pingResult.MaxCost = int(stats.MaxRtt / time.Millisecond)
|
||||
pingResult.MinCost = int(stats.MinRtt / time.Millisecond)
|
||||
pingResult.Lost = int(stats.PacketLoss)
|
||||
pingResult.PingCount = pingOptions.Count
|
||||
pingResult.PacketSize = pinger.Size
|
||||
pingResult.ReceivePacketCount = stats.PacketsRecv
|
||||
pingResult.SendPacketCount = stats.PacketsSent
|
||||
pingResult.SuccessCount = stats.PacketsRecv
|
||||
pingResult.Suc = true
|
||||
pingResult.ErrMsg = ""
|
||||
return
|
||||
}
|
||||
20
hrp/internal/dial/traceroute.go
Normal file
20
hrp/internal/dial/traceroute.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package dial
|
||||
|
||||
type TraceRouteOptions struct {
|
||||
MaxTTL int
|
||||
Queries int
|
||||
SaveTests bool
|
||||
}
|
||||
|
||||
type TraceRouteResult struct {
|
||||
IP string `json:"ip"`
|
||||
Details []TraceRouteResultNode `json:"details"`
|
||||
Suc bool `json:"suc"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
}
|
||||
|
||||
type TraceRouteResultNode struct {
|
||||
Id int `json:"id"`
|
||||
Ip string `json:"ip"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
106
hrp/internal/dial/traceroute_unix.go
Normal file
106
hrp/internal/dial/traceroute_unix.go
Normal file
@@ -0,0 +1,106 @@
|
||||
//go:build darwin || linux
|
||||
// +build darwin linux
|
||||
|
||||
package dial
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
var (
|
||||
regexIPAddr = regexp.MustCompile(`([\d.]+)`)
|
||||
regexElapsedTime = regexp.MustCompile(`(\d+\.\d+)`)
|
||||
regexTraceroutePass = regexp.MustCompile(fmt.Sprintf(`(\d+)[\s*]+(\S+)\s+\(%s\)\s+%s\s+ms`, regexIPAddr, regexElapsedTime))
|
||||
regexTracerouteFailure = regexp.MustCompile(`(\d+)[\s*]+$`)
|
||||
)
|
||||
|
||||
func DoTraceRoute(traceRouteOptions *TraceRouteOptions, args []string) (err error) {
|
||||
if len(args) != 1 {
|
||||
return errors.New("there should be one argument")
|
||||
}
|
||||
var traceRouteResult TraceRouteResult
|
||||
defer func() {
|
||||
if traceRouteOptions.SaveTests {
|
||||
dir, _ := os.Getwd()
|
||||
traceRouteResultName := fmt.Sprintf("traceroute_result_%v.json", time.Now().Format("20060102150405"))
|
||||
traceRouteResultPath := filepath.Join(dir, traceRouteResultName)
|
||||
err = builtin.Dump2JSON(traceRouteResult, traceRouteResultPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("save traceroute result failed")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
traceRouteTarget := args[0]
|
||||
parsedURL, err := url.Parse(traceRouteTarget)
|
||||
if err == nil && parsedURL.Host != "" {
|
||||
log.Info().Msgf("parse input url %v and extract host %v", traceRouteTarget, parsedURL.Host)
|
||||
traceRouteTarget = strings.Split(parsedURL.Host, ":")[0]
|
||||
}
|
||||
|
||||
cmd := exec.Command("traceroute", "-m", strconv.Itoa(traceRouteOptions.MaxTTL),
|
||||
"-q", strconv.Itoa(traceRouteOptions.Queries), traceRouteTarget)
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
startT := time.Now()
|
||||
defer func() {
|
||||
log.Info().Msgf("for target %s, traceroute costs %v", traceRouteTarget, time.Since(startT))
|
||||
}()
|
||||
|
||||
log.Info().Msgf("start to traceroute %v", traceRouteTarget)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
traceRouteResult.Suc = false
|
||||
traceRouteResult.ErrMsg = "execute traceroute failed"
|
||||
log.Error().Err(err).Msg("start command failed")
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
hopLine := scanner.Text()
|
||||
fmt.Println(hopLine)
|
||||
failureLine := regexTracerouteFailure.FindStringSubmatch(hopLine)
|
||||
if len(failureLine) == 2 {
|
||||
hopID, _ := strconv.Atoi(failureLine[1])
|
||||
traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{
|
||||
Id: hopID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
passLine := regexTraceroutePass.FindStringSubmatch(hopLine)
|
||||
if len(passLine) == 5 {
|
||||
hopID, _ := strconv.Atoi(passLine[1])
|
||||
traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{
|
||||
Id: hopID,
|
||||
Ip: passLine[3],
|
||||
Time: passLine[4],
|
||||
})
|
||||
traceRouteResult.Suc = true
|
||||
}
|
||||
}
|
||||
hopCount := len(traceRouteResult.Details)
|
||||
traceRouteResult.IP = traceRouteResult.Details[hopCount-1].Ip
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
traceRouteResult.Suc = false
|
||||
traceRouteResult.ErrMsg = "wait traceroute finish failed"
|
||||
log.Error().Err(err).Msg("wait command failed")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
105
hrp/internal/dial/traceroute_windows.go
Normal file
105
hrp/internal/dial/traceroute_windows.go
Normal file
@@ -0,0 +1,105 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dial
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
var (
|
||||
regexTracertPass = regexp.MustCompile(`(\d+)[\s*<]+(\d+)\s+ms`)
|
||||
regexTracertFailure = regexp.MustCompile(`(\d+)[\s*]+Request timed out`)
|
||||
)
|
||||
|
||||
func DoTraceRoute(traceRouteOptions *TraceRouteOptions, args []string) (err error) {
|
||||
if len(args) != 1 {
|
||||
return errors.New("there should be one argument")
|
||||
}
|
||||
var traceRouteResult TraceRouteResult
|
||||
defer func() {
|
||||
if traceRouteOptions.SaveTests {
|
||||
dir, _ := os.Getwd()
|
||||
traceRouteResultName := fmt.Sprintf("traceroute_result_%v.json", time.Now().Format("20060102150405"))
|
||||
traceRouteResultPath := filepath.Join(dir, traceRouteResultName)
|
||||
err = builtin.Dump2JSON(traceRouteResult, traceRouteResultPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("save traceroute result failed")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
traceRouteTarget := args[0]
|
||||
parsedURL, err := url.Parse(traceRouteTarget)
|
||||
if err == nil && parsedURL.Host != "" {
|
||||
log.Info().Msgf("parse input url %v and extract host %v", traceRouteTarget, parsedURL.Host)
|
||||
traceRouteTarget = strings.Split(parsedURL.Host, ":")[0]
|
||||
}
|
||||
|
||||
cmd := exec.Command("tracert", "-h", strconv.Itoa(traceRouteOptions.MaxTTL), traceRouteTarget)
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
startT := time.Now()
|
||||
defer func() {
|
||||
log.Info().Msgf("for target %s, traceroute costs %v", traceRouteTarget, time.Since(startT))
|
||||
}()
|
||||
|
||||
log.Info().Msgf("start to traceroute %v", traceRouteTarget)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
traceRouteResult.Suc = false
|
||||
traceRouteResult.ErrMsg = "execute traceroute failed"
|
||||
log.Error().Err(err).Msg("start command failed")
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
hopLine := scanner.Text()
|
||||
fmt.Println(hopLine)
|
||||
failureLine := regexTracertFailure.FindStringSubmatch(hopLine)
|
||||
if len(failureLine) == 2 {
|
||||
hopID, _ := strconv.Atoi(failureLine[1])
|
||||
traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{
|
||||
Id: hopID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
passLine := regexTracertPass.FindStringSubmatch(hopLine)
|
||||
if len(passLine) == 3 {
|
||||
hopID, _ := strconv.Atoi(passLine[1])
|
||||
fields := strings.Fields(hopLine)
|
||||
hopIP := strings.Trim(fields[len(fields)-1], "[]")
|
||||
traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{
|
||||
Id: hopID,
|
||||
Ip: hopIP,
|
||||
Time: passLine[2],
|
||||
})
|
||||
traceRouteResult.Suc = true
|
||||
}
|
||||
}
|
||||
hopCount := len(traceRouteResult.Details)
|
||||
traceRouteResult.IP = traceRouteResult.Details[hopCount-1].Ip
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
traceRouteResult.Suc = false
|
||||
traceRouteResult.ErrMsg = "wait traceroute finish failed"
|
||||
log.Error().Err(err).Msg("wait command failed")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user