diff --git a/go.mod b/go.mod index 51da4a79..73df7b9d 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 + github.com/gizak/termui v2.3.0+incompatible // indirect github.com/go-errors/errors v1.0.1 github.com/go-openapi/spec v0.20.6 github.com/go-ping/ping v1.1.0 @@ -18,8 +19,12 @@ 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/maruel/panicparse v1.6.2 // indirect + github.com/mehrdadrad/mylg v0.2.6 github.com/miekg/dns v1.0.14 + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.4.1 + github.com/nsf/termbox-go v1.1.1 // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.0 @@ -33,6 +38,7 @@ require ( golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.28.0 + gopkg.in/h2non/gock.v0 v0.1.6 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 09453f97..7140eef6 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,11 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -120,6 +123,8 @@ github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LKdiuDWQpjqw= +github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -325,9 +330,13 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maja42/goval v1.2.1 h1:fyEgzddqPgCZsKcFLk4C6SdCHyEaAHYvtZG4mGzQOHU= github.com/maja42/goval v1.2.1/go.mod h1:42LU+BQXL/veE9jnTTUOSj38GRmOTSThYSXRVodI5J4= +github.com/maruel/panicparse v1.6.2 h1:tZuGQTlbOY5jCprrWMJTikREqKPn+UAKdR4CHSpj834= +github.com/maruel/panicparse v1.6.2/go.mod h1:uoxI4w9gJL6XahaYPMq/z9uadrdr1SyHuQwV2q80Mm0= +github.com/maruel/panicparse/v2 v2.1.1/go.mod h1:AeTWdCE4lcq8OKsLb6cHSj1RWHVSnV9HBCk7sKLF4Jg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= @@ -345,6 +354,9 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 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/mehrdadrad/mylg v0.2.6 h1:MsIreb998Yn/T9h2u92fqPMDZrAO+4720swtfjEqbKM= +github.com/mehrdadrad/mylg v0.2.6/go.mod h1:mh70kG4nkk0dgfP+jRnkCX3E9wOi/bvJTmtiBlFmai4= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 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= @@ -354,6 +366,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -376,6 +390,8 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= +github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -662,6 +678,7 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -882,6 +899,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/h2non/gock.v0 v0.1.6 h1:vKwe08YIEZEZtspT36nTlo9S+edPFwU6k1tWC7mTw3A= +gopkg.in/h2non/gock.v0 v0.1.6/go.mod h1:KjTyaFK6xOUSpvIeLQDkMc/AyfyRKVKXhFxT4QxkM3Q= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= diff --git a/hrp/cmd/dial.go b/hrp/cmd/dial.go index 5f394db9..68ffc3d5 100644 --- a/hrp/cmd/dial.go +++ b/hrp/cmd/dial.go @@ -10,8 +10,9 @@ import ( ) var ( - pingOptions dial.PingOptions - dnsOptions dial.DnsOptions + pingOptions dial.PingOptions + dnsOptions dial.DnsOptions + traceRouteOptions dial.TraceRouteOptions ) var pingCmd = &cobra.Command{ @@ -44,16 +45,31 @@ var dnsCmd = &cobra.Command{ }, } +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 { + return dial.DoTraceRoute(&traceRouteOptions, 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 results json") + 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 results json") + dnsCmd.Flags().BoolVar(&dnsOptions.SaveTests, "save-tests", false, "Save DNS resolution result as json") + + rootCmd.AddCommand(traceRouteCmd) + traceRouteCmd.Flags().BoolVar(&traceRouteOptions.SaveTests, "save-tests", false, "Save traceroute result as json") } diff --git a/hrp/internal/dial/dns.go b/hrp/internal/dial/dns.go index 7c169742..d91141d0 100644 --- a/hrp/internal/dial/dns.go +++ b/hrp/internal/dial/dns.go @@ -214,7 +214,7 @@ func DoDns(dnsOptions *DnsOptions, args []string) (err error) { dnsResultPath := filepath.Join(dir, dnsResultName) err = builtin.Dump2JSON(dnsResult, dnsResultPath) if err != nil { - log.Error().Err(err).Msg("save ping result failed") + log.Error().Err(err).Msg("save dns resolution result failed") } } }() diff --git a/hrp/internal/dial/traceroute.go b/hrp/internal/dial/traceroute.go new file mode 100644 index 00000000..5f19c4a1 --- /dev/null +++ b/hrp/internal/dial/traceroute.go @@ -0,0 +1,138 @@ +package dial + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/mehrdadrad/mylg/cli" + "github.com/mehrdadrad/mylg/icmp" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/json" +) + +type TraceRouteOptions struct { + 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"` +} + +type HopResp struct { + Num int `json:"Id"` + Hop string `json:"Hop"` + Ip string `json:"Ip"` + Elapsed float64 `json:"Elapsed"` + Holder string `json:"Holder"` + ASN float64 `json:"ASN"` + Last bool `json:"Last"` +} + +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] + } + + cfg, err := cli.ReadDefaultConfig() + if err != nil { + log.Error().Err(err).Msgf("fail to read default config") + traceRouteResult.Suc = false + traceRouteResult.ErrMsg = err.Error() + return + } + + traceRouter, err := icmp.NewTrace(traceRouteTarget, cfg) + if err != nil { + log.Error().Err(err).Msgf("fail to new traceRouter for %s", traceRouteTarget) + traceRouteResult.Suc = false + traceRouteResult.ErrMsg = err.Error() + return + } + + startT := time.Now() + defer func() { + log.Info().Msgf("for target %s, traceroute costs %v", traceRouteTarget, time.Since(startT)) + }() + + log.Info().Msgf("start to trace route of %v", traceRouteTarget) + hopRespChan, err := traceRouter.MRun() + if err != nil { + log.Error().Err(err).Msgf("fail to trace route of %v", traceRouteTarget) + traceRouteResult.Suc = false + traceRouteResult.ErrMsg = err.Error() + } + count := 0 + t := time.NewTicker(2 * time.Minute) + for { + select { + case <-t.C: + log.Error().Err(err).Msgf("fail to do traceroute for %s because timeout", traceRouteTarget) + traceRouteResult.Suc = false + traceRouteResult.ErrMsg = "timeout" + return + case resp := <-hopRespChan: + respJSON := resp.Marshal() + fmt.Printf("traceroute hop: %v\n", respJSON) + var hopResp HopResp + err = json.Unmarshal([]byte(respJSON), &hopResp) + if err != nil { + log.Error().Err(err).Msgf("fail to do traceroute for %s because of hop response %v unmarshal error", traceRouteTarget, respJSON) + traceRouteResult.Suc = false + traceRouteResult.ErrMsg = "hop response unmarshal error" + } + traceRouteResult.Details = append(traceRouteResult.Details, TraceRouteResultNode{ + Id: hopResp.Num, + Ip: hopResp.Ip, + Time: fmt.Sprintf("%.2f", hopResp.Elapsed), + }) + traceRouteResult.Suc = true + traceRouteResult.ErrMsg = "" + if hopResp.Last { + traceRouteResult.IP = hopResp.Ip + log.Info().Msgf("for target %s, traceroute completed", traceRouteTarget) + return + } + count += 1 + if count > 30 { + log.Info().Msgf("for target %s, traceroute hop counts reach limit", traceRouteTarget) + return + } + } + } +}