├── .gitignore ├── .goreleaser.yaml ├── Dockerfile ├── README.md ├── common ├── qqwry.go ├── qqwry_test.go ├── types.go ├── utils.go └── utils_test.go ├── example └── simple.go ├── go.mod ├── go.sum ├── icmp └── icmp.go ├── logo.png ├── main.go ├── mtr ├── mtr.go └── types.go ├── ping ├── ping.go └── types.go ├── qqwry.dat └── spew └── spew.go /.gitignore: -------------------------------------------------------------------------------- 1 | gomtr 2 | dist 3 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | goarch: 16 | - amd64 17 | - arm64 18 | 19 | archives: 20 | - format: tar.gz 21 | # this name template makes the OS and Arch compatible with the results of uname. 22 | name_template: >- 23 | {{ .ProjectName }}_v{{ .Version }}_ 24 | {{- title .Os }}_ 25 | {{- if eq .Arch "amd64" }}x86_64 26 | {{- else if eq .Arch "386" }}i386 27 | {{- else }}{{ .Arch }}{{ end }} 28 | {{- if .Arm }}v{{ .Arm }}{{ end }} 29 | # use zip for windows archives 30 | format_overrides: 31 | - goos: windows 32 | format: zip 33 | checksum: 34 | name_template: 'checksums.txt' 35 | snapshot: 36 | name_template: "{{ incpatch .Version }}-next" 37 | changelog: 38 | sort: asc 39 | filters: 40 | exclude: 41 | - '^docs:' 42 | - '^test:' 43 | 44 | # The lines beneath this are called `modelines`. See `:help modeline` 45 | # Feel free to remove those if you don't want/use them. 46 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 47 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | RUN apk add --no-cache make git 4 | WORKDIR /gomtr-src 5 | COPY . /gomtr-src 6 | RUN go mod download && \ 7 | go build . && \ 8 | mv gomtr /gomtr 9 | 10 | FROM alpine:latest 11 | 12 | RUN apk add --no-cache ca-certificates 13 | COPY --from=builder /gomtr / 14 | ENTRYPOINT ["/gomtr"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gomtr 2 | 3 | 4 | 5 | golang mtr and ping tools 6 | 7 | - simple library api 8 | - support domain name 9 | - support geo info 10 | - concurrently ping 11 | - concurrently mtr 12 | - ... 13 | 14 | ### update qqwry.dat 15 | 16 | `使用QQ纯真库解析ip地址位置` 17 | 18 | **get qqwry.dat from remote** 19 | 20 | ```bash 21 | wget http://update.cz88.net/soft/setup.zip 22 | unzip setup.zip 23 | mkdir ~/gomtr/ 24 | cp qqwry.dat ~/gomtr/ 25 | ``` 26 | 27 | **get qqwry.dat from local** 28 | 29 | ``` 30 | mkdir ~/gomtr/ 31 | cp qqwry.dat ~/gomtr/ 32 | ``` 33 | 34 | ### Install 35 | 36 | gomtr Requires `Go >= 1.17` 37 | 38 | **install command** 39 | 40 | ```bash 41 | go get -u -v github.com/rfyiamcool/gomtr 42 | ``` 43 | 44 | **local build** 45 | 46 | ```bash 47 | go build . 48 | ``` 49 | 50 | ### Usage 51 | 52 | **print help** 53 | 54 | ``` 55 | > go build . 56 | > ./gomtr -h 57 | 58 | Usage: gomtr [-hvc] [-mtr] [-ping] hostname list 59 | 60 | Options: 61 | -c int 62 | run count (default 3) 63 | -h print help() 64 | -mtr 65 | handle mtr (default true) 66 | -ping 67 | handle ping 68 | -v verbose logging 69 | ``` 70 | 71 | **test mtr command** 72 | 73 | ```bash 74 | sudo gomtr 8.8.8.8 75 | ``` 76 | 77 | **terminal stdout** 78 | 79 | ```bash 80 | Start: 2020-09-06 02:48:50, DestAddr: 8.8.8.8 81 | HOST Loss% Snt Last Avg Best Wrst GEO 82 | 1 192.168.124.1 0.0% 3 3.00 7.41 2.03 17.19 局域网:对方和您在同一内部网 83 | 2 192.168.1.1 0.0% 3 2.12 3.35 2.12 3.99 局域网:对方和您在同一内部网 84 | 3 61.48.43.1 0.0% 3 4.61 4.55 4.36 4.69 北京市:联通 85 | 4 221.129.255.57 0.0% 3 4.20 5.93 4.20 8.33 天津市:广电网 86 | 5 61.51.169.113 0.0% 3 6.67 6.66 6.61 6.71 北京市:联通 87 | 6 61.49.214.1 0.0% 3 21.53 14.10 9.94 21.53 北京市:联通 88 | 7 219.158.15.34 33.3% 3 41.87 43.61 41.87 45.35 中国:联通骨干网 89 | 8 219.158.8.114 0.0% 3 44.21 66.83 43.70 112.58 中国:联通骨干网 90 | 9 219.158.24.134 0.0% 3 51.57 49.33 44.26 52.16 广东省广州市:联通骨干网互联节点 91 | 10 219.158.10.30 0.0% 3 50.30 50.09 49.92 50.30 中国:联通骨干网 92 | 11 219.158.33.174 0.0% 3 61.77 109.47 44.95 221.69 中国:联通骨干网 93 | 12 108.170.241.1 0.0% 3 166.33 87.78 46.81 166.33 香港:特别行政区 94 | 13 72.14.233.169 0.0% 3 58.46 50.55 44.45 58.46 美国:加利福尼亚州圣克拉拉县山景市谷歌公司 95 | 14 8.8.8.8 0.0% 3 48.34 110.89 47.30 237.04 美国:加利福尼亚州圣克拉拉县山景市谷歌公司DNS服务器 96 | ``` 97 | -------------------------------------------------------------------------------- /common/qqwry.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "sync" 7 | 8 | "github.com/mitchellh/go-homedir" 9 | "github.com/yinheli/qqwry" 10 | ) 11 | 12 | var ( 13 | home, _ = homedir.Dir() 14 | wrydataPath = path.Join(home, "gomtr/qqwry.dat") 15 | 16 | mu sync.Mutex 17 | geoipParser = buildGeoipParser() 18 | ) 19 | 20 | func GetIpInfo(ip string) (string, string, error) { 21 | if geoipParser == nil { 22 | return "", "", nil 23 | } 24 | 25 | // qqwry don't support thread safe. 26 | mu.Lock() 27 | defer mu.Unlock() 28 | 29 | geoipParser.Find(ip) 30 | return geoipParser.Country, geoipParser.City, nil 31 | } 32 | 33 | func buildGeoipParser() *qqwry.QQwry { 34 | _, err := os.Stat(wrydataPath) 35 | if os.IsNotExist(err) { 36 | return nil 37 | } 38 | 39 | return qqwry.NewQQwry(wrydataPath) 40 | } 41 | -------------------------------------------------------------------------------- /common/qqwry_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/yinheli/qqwry" 8 | ) 9 | 10 | func TestQueryIP(t *testing.T) { 11 | q := qqwry.NewQQwry("../qqwry.dat") 12 | q.Find("8.8.8.8") 13 | log.Printf("ip:%v, Country:%v, City:%v", q.Ip, q.Country, q.City) 14 | } 15 | -------------------------------------------------------------------------------- /common/types.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type IcmpResp struct { 8 | Success bool 9 | Addr string 10 | Elapsed time.Duration 11 | } 12 | 13 | type IcmpHop struct { 14 | Success bool 15 | Address string 16 | Host string 17 | N int 18 | TTL int 19 | Snt int 20 | LastTime time.Duration 21 | AvgTime time.Duration 22 | BestTime time.Duration 23 | WrstTime time.Duration 24 | Loss float32 25 | } 26 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "runtime" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/rfyiamcool/gomtr/spew" 12 | ) 13 | 14 | // LookupAddrs nslookup domain name, return ips 15 | func LookupIps(addr string) ([]string, error) { 16 | addrs, err := net.LookupHost(addr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | ips := make([]string, 0) 22 | for _, addr := range addrs { 23 | ipaddr, err := net.ResolveIPAddr("ip", addr) 24 | if err != nil { 25 | continue 26 | } 27 | 28 | ips = append(ips, ipaddr.IP.String()) 29 | } 30 | 31 | return ips, nil 32 | } 33 | 34 | func Goid() int { 35 | defer func() { 36 | if err := recover(); err != nil { 37 | spew.Errorf("panic recover, err: %v", err) 38 | } 39 | }() 40 | 41 | var buf [64]byte 42 | n := runtime.Stack(buf[:], false) 43 | idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] 44 | id, err := strconv.Atoi(idField) 45 | if err != nil { 46 | panic(fmt.Sprintf("cannot get goroutine id: %v", err)) 47 | } 48 | return id 49 | } 50 | 51 | func IsEqualIp(ips1, ips2 string) bool { 52 | ip1 := net.ParseIP(ips1) 53 | if ip1 == nil { 54 | return false 55 | } 56 | 57 | ip2 := net.ParseIP(ips2) 58 | if ip2 == nil { 59 | return false 60 | } 61 | 62 | if ip1.String() != ip2.String() { 63 | return false 64 | } 65 | 66 | return true 67 | } 68 | 69 | func Time2Float(t time.Duration) float32 { 70 | return (float32)(t/time.Microsecond) / float32(1000) 71 | } 72 | -------------------------------------------------------------------------------- /common/utils_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLookupIps(t *testing.T) { 10 | ns, err := LookupIps("baidu.com") 11 | assert.Equal(t, err, nil) 12 | assert.Greater(t, len(ns), 0) 13 | 14 | ns, err = LookupIps("8.8.8.8") 15 | assert.Equal(t, err, nil) 16 | assert.Equal(t, len(ns), 1) 17 | assert.Equal(t, ns[0], "8.8.8.8") 18 | } 19 | -------------------------------------------------------------------------------- /example/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/rfyiamcool/gomtr/mtr" 9 | "github.com/rfyiamcool/gomtr/ping" 10 | ) 11 | 12 | var ( 13 | count = 3 14 | targets = []string{"47.252.95.42", "47.75.18.65", "47.104.38.82", "47.88.73.1", "47.91.8.42"} 15 | ) 16 | 17 | func main() { 18 | for _, addr := range targets { 19 | fmt.Println("start detect addr: ", addr) 20 | 21 | wg := sync.WaitGroup{} 22 | wg.Add(1) 23 | go func(target string) { 24 | defer wg.Done() 25 | 26 | for i := 0; i < count; i++ { 27 | mm, err := mtr.Mtr(target, 30, 10, 800) 28 | if err != nil { 29 | fmt.Println(err) 30 | } 31 | fmt.Println(mm) 32 | } 33 | }(addr) 34 | 35 | wg.Add(1) 36 | go func(target string) { 37 | defer wg.Done() 38 | 39 | for i := 0; i < count; i++ { 40 | mm, err := ping.Ping(target, 1, 1000, 1) 41 | if err != nil { 42 | fmt.Println(err) 43 | } 44 | fmt.Println(mm) 45 | time.Sleep(1 * time.Second) 46 | } 47 | }(addr) 48 | 49 | wg.Wait() 50 | } 51 | 52 | select {} 53 | } 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rfyiamcool/gomtr 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/fatih/color v1.9.0 8 | github.com/mitchellh/go-homedir v1.1.0 9 | github.com/stretchr/testify v1.6.1 10 | github.com/yinheli/qqwry v0.0.0-20160229183603-f50680010f4a 11 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 12 | ) 13 | 14 | require ( 15 | github.com/mattn/go-colorable v0.1.4 // indirect 16 | github.com/mattn/go-isatty v0.0.11 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc // indirect 19 | golang.org/x/sys v0.6.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 5 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 6 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 7 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 8 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 9 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 10 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 11 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 12 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 17 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 18 | github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc h1:7VHQaaNwHymWbj8lAcXMYX1qopebSBHwYC3ceXLWONU= 19 | github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc/go.mod h1:Pcc297eVCbkDBBVq8FbnI+qDUeIMrHy4Bo7nveAuCAs= 20 | github.com/yinheli/qqwry v0.0.0-20160229183603-f50680010f4a h1:VUPXGL4N1B5xqomxI4vZn0momgleh0hcH0PE/oIOsAI= 21 | github.com/yinheli/qqwry v0.0.0-20160229183603-f50680010f4a/go.mod h1:Zva9ErVtC2arl+9xtHwiXujgejX1S3VpOYExADJ9kio= 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 23 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 24 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 25 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= 26 | golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 33 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 38 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 39 | -------------------------------------------------------------------------------- /icmp/icmp.go: -------------------------------------------------------------------------------- 1 | package icmp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "golang.org/x/net/icmp" 10 | "golang.org/x/net/ipv4" 11 | "golang.org/x/net/ipv6" 12 | 13 | "github.com/rfyiamcool/gomtr/common" 14 | ) 15 | 16 | const ( 17 | ProtocolICMP = 1 // Internet Control Message 18 | ProtocolIPv6ICMP = 58 // ICMP for IPv6 19 | ) 20 | 21 | func Icmp(destAddr string, ttl, pid int, timeout time.Duration, seq int) (hop common.IcmpResp, err error) { 22 | ip := net.ParseIP(destAddr) 23 | if ip == nil { 24 | return hop, fmt.Errorf("ip: %v is invalid", destAddr) 25 | } 26 | 27 | ipaddr := net.IPAddr{IP: ip} 28 | if p4 := ip.To4(); len(p4) == net.IPv4len { 29 | return icmpIpv4("0.0.0.0", &ipaddr, ttl, pid, timeout, seq) // need sudo permission 30 | } else { 31 | return icmpIpv6("::", &ipaddr, ttl, pid, timeout, seq) 32 | } 33 | } 34 | 35 | func icmpIpv4(localAddr string, dst net.Addr, ttl, pid int, timeout time.Duration, seq int) (hop common.IcmpResp, err error) { 36 | hop.Success = false 37 | start := time.Now() 38 | c, err := icmp.ListenPacket("ip4:icmp", localAddr) 39 | if err != nil { 40 | return hop, err 41 | } 42 | defer c.Close() 43 | 44 | if err = c.IPv4PacketConn().SetTTL(ttl); err != nil { 45 | return hop, err 46 | } 47 | 48 | if err = c.SetDeadline(time.Now().Add(timeout)); err != nil { 49 | return hop, err 50 | } 51 | 52 | bs := make([]byte, 4) 53 | binary.LittleEndian.PutUint32(bs, uint32(seq)) 54 | wm := icmp.Message{ 55 | Type: ipv4.ICMPTypeEcho, 56 | Code: 0, 57 | Body: &icmp.Echo{ 58 | ID: pid, Seq: seq, 59 | Data: append(bs, 'x'), 60 | }, 61 | } 62 | 63 | wb, err := wm.Marshal(nil) 64 | if err != nil { 65 | return hop, err 66 | } 67 | 68 | if _, err := c.WriteTo(wb, dst); err != nil { 69 | return hop, err 70 | } 71 | 72 | peer, _, err := listenForSpecific4(c, append(bs, 'x'), pid, seq) 73 | if err != nil { 74 | return hop, err 75 | } 76 | 77 | elapsed := time.Since(start) 78 | hop.Elapsed = elapsed 79 | hop.Addr = peer 80 | hop.Success = true 81 | return hop, err 82 | } 83 | 84 | func icmpIpv6(localAddr string, dst net.Addr, ttl, pid int, timeout time.Duration, seq int) (hop common.IcmpResp, err error) { 85 | hop.Success = false 86 | start := time.Now() 87 | c, err := icmp.ListenPacket("ip6:ipv6-icmp", localAddr) 88 | if err != nil { 89 | return hop, err 90 | } 91 | defer c.Close() 92 | 93 | if err = c.IPv6PacketConn().SetHopLimit(ttl); err != nil { 94 | return hop, err 95 | } 96 | 97 | if err = c.SetDeadline(time.Now().Add(timeout)); err != nil { 98 | return hop, err 99 | } 100 | 101 | bs := make([]byte, 4) 102 | binary.LittleEndian.PutUint32(bs, uint32(seq)) 103 | wm := icmp.Message{ 104 | Type: ipv6.ICMPTypeEchoRequest, 105 | Code: 0, 106 | Body: &icmp.Echo{ 107 | ID: pid, Seq: seq, 108 | Data: append(bs, 'x'), 109 | }, 110 | } 111 | wb, err := wm.Marshal(nil) 112 | if err != nil { 113 | return hop, err 114 | } 115 | 116 | if _, err := c.WriteTo(wb, dst); err != nil { 117 | return hop, err 118 | } 119 | 120 | peer, _, err := listenForSpecific6(c, append(bs, 'x'), pid, seq) 121 | if err != nil { 122 | return hop, err 123 | } 124 | 125 | elapsed := time.Since(start) 126 | hop.Elapsed = elapsed 127 | hop.Addr = peer 128 | hop.Success = true 129 | return hop, err 130 | } 131 | 132 | func listenForSpecific4(conn *icmp.PacketConn, neededBody []byte, needId, needSeq int) (string, []byte, error) { 133 | for { 134 | b := make([]byte, 1500) 135 | n, peer, err := conn.ReadFrom(b) 136 | if err != nil { 137 | if neterr, ok := err.(*net.OpError); ok { 138 | return "", []byte{}, neterr 139 | } 140 | } 141 | if n == 0 { 142 | continue 143 | } 144 | 145 | x, err := icmp.ParseMessage(ProtocolICMP, b[:n]) 146 | if err != nil { 147 | continue 148 | } 149 | 150 | if typ, ok := x.Type.(ipv4.ICMPType); ok && typ.String() == "time exceeded" { 151 | body := x.Body.(*icmp.TimeExceeded).Data 152 | 153 | x, _ := icmp.ParseMessage(ProtocolICMP, body[20:]) 154 | switch x.Body.(type) { 155 | case *icmp.Echo: 156 | msg := x.Body.(*icmp.Echo) 157 | if msg.ID == needId && msg.Seq == needSeq { 158 | return peer.String(), []byte{}, nil 159 | } 160 | default: 161 | // ignore 162 | } 163 | } 164 | 165 | if typ, ok := x.Type.(ipv4.ICMPType); ok && typ.String() == "echo reply" { 166 | b, _ := x.Body.Marshal(ProtocolICMP) 167 | if string(b[4:]) != string(neededBody) || x.Body.(*icmp.Echo).ID != needId { 168 | continue 169 | } 170 | 171 | return peer.String(), b[4:], nil 172 | } 173 | } 174 | } 175 | 176 | func listenForSpecific6(conn *icmp.PacketConn, neededBody []byte, needId, needSeq int) (string, []byte, error) { 177 | for { 178 | b := make([]byte, 1500) 179 | n, peer, err := conn.ReadFrom(b) 180 | if err != nil { 181 | if neterr, ok := err.(*net.OpError); ok { 182 | return "", []byte{}, neterr 183 | } 184 | } 185 | if n == 0 { 186 | continue 187 | } 188 | 189 | x, err := icmp.ParseMessage(ProtocolIPv6ICMP, b[:n]) 190 | if err != nil { 191 | continue 192 | } 193 | 194 | if x.Type.(ipv6.ICMPType) == ipv6.ICMPTypeTimeExceeded { 195 | body := x.Body.(*icmp.TimeExceeded).Data 196 | x, _ := icmp.ParseMessage(ProtocolIPv6ICMP, body[40:]) 197 | switch x.Body.(type) { 198 | case *icmp.Echo: 199 | msg := x.Body.(*icmp.Echo) 200 | if msg.ID == needId && msg.Seq == needSeq { 201 | return peer.String(), []byte{}, nil 202 | } 203 | default: 204 | // ignore 205 | } 206 | } 207 | 208 | if typ, ok := x.Type.(ipv6.ICMPType); ok && typ == ipv6.ICMPTypeEchoReply { 209 | b, _ := x.Body.Marshal(1) 210 | if string(b[4:]) != string(neededBody) || x.Body.(*icmp.Echo).ID != needId { 211 | continue 212 | } 213 | 214 | return peer.String(), b[4:], nil 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfyiamcool/gomtr/95d50030c33fac84349538a2bc3afdf2a37e7479/logo.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/rfyiamcool/gomtr/common" 10 | "github.com/rfyiamcool/gomtr/mtr" 11 | "github.com/rfyiamcool/gomtr/spew" 12 | ) 13 | 14 | var ( 15 | count = 3 16 | help bool 17 | verbose bool 18 | ping bool 19 | mtrFlag bool 20 | targets []string 21 | ) 22 | 23 | func init() { 24 | flag.BoolVar(&help, "h", false, "print help()") 25 | flag.BoolVar(&verbose, "v", false, "verbose logging") 26 | flag.BoolVar(&mtrFlag, "mtr", true, "handle mtr") 27 | flag.BoolVar(&ping, "ping", false, "handle ping") 28 | flag.IntVar(&count, "c", 3, "run count") 29 | flag.Usage = usage 30 | } 31 | 32 | func usage() { 33 | fmt.Fprintf(os.Stdout, `Usage: gomtr [-hvc] [-mtr] [-ping] hostname list 34 | 35 | Options: 36 | `) 37 | flag.PrintDefaults() 38 | } 39 | 40 | func parseCommand() { 41 | flag.Parse() 42 | if help { 43 | flag.Usage() 44 | return 45 | } 46 | args := flag.Args() 47 | if len(args) == 0 { 48 | fmt.Fprintln(os.Stderr, errors.New("miss target params")) 49 | os.Exit(2) 50 | } 51 | targets = args 52 | } 53 | 54 | func main() { 55 | parseCommand() 56 | 57 | for _, addr := range targets { 58 | ips, err := common.LookupIps(addr) 59 | if err != nil { 60 | spew.Errorf("faild to dnsresolv addr %s, err: %v", addr, err) 61 | continue 62 | } 63 | if len(ips) == 0 { 64 | spew.Errorf("can't get available ipaddrs with addr %s", addr) 65 | continue 66 | } 67 | 68 | mm, err := mtr.Mtr(ips[0], 30, 3, 800) 69 | if err != nil { 70 | spew.Error(err) 71 | } 72 | spew.Debug(mm) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mtr/mtr.go: -------------------------------------------------------------------------------- 1 | package mtr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/rfyiamcool/gomtr/common" 10 | "github.com/rfyiamcool/gomtr/icmp" 11 | "github.com/rfyiamcool/gomtr/spew" 12 | ) 13 | 14 | func Mtr(ipAddr string, maxHops, sntSize, timeoutMs int) (result string, err error) { 15 | options := MtrOptions{} 16 | options.SetMaxHops(maxHops) 17 | options.SetSntSize(sntSize) 18 | options.SetTimeoutMs(timeoutMs) 19 | 20 | var out MtrResult 21 | var buffer bytes.Buffer 22 | buffer.WriteString(fmt.Sprintf("Start: %v, DestAddr: %v\n\n", time.Now().Format("2006-01-02 15:04:05"), ipAddr)) 23 | out, err = runMtr(ipAddr, &options) 24 | if err != nil { 25 | buffer.WriteString(fmt.Sprintf("mtr failed due to an error: %v\n", err)) 26 | return buffer.String(), err 27 | } 28 | if len(out.Hops) == 0 { 29 | buffer.WriteString("mtr failed. Expected at least one hop\n") 30 | return buffer.String(), nil 31 | } 32 | 33 | buffer.WriteString(fmt.Sprintf("%-3v %-15v %10v%c %10v %10v %10v %10v %10v %-100v \n", "", "HOST", "Loss", '%', "Snt", "Last", "Avg", "Best", "Wrst", "GEO")) 34 | 35 | var hopStr string 36 | var lastHop int 37 | for index, hop := range out.Hops { 38 | if hop.Success { 39 | if hopStr != "" { 40 | buffer.WriteString(hopStr) 41 | hopStr = "" 42 | } 43 | 44 | buffer.WriteString(fmt.Sprintf("%-3d %-15v %10.1f%c %10v %10.2f %10.2f %10.2f %10.2f %-100v \n", hop.TTL, hop.Address, hop.Loss, '%', hop.Snt, common.Time2Float(hop.LastTime), common.Time2Float(hop.AvgTime), common.Time2Float(hop.BestTime), common.Time2Float(hop.WrstTime), geoFor(hop.Address))) 45 | lastHop = hop.TTL 46 | } else { 47 | if index != len(out.Hops)-1 { 48 | hopStr += fmt.Sprintf("%-3d %-15v %10.1f%c %10v %10.2f %10.2f %10.2f %10.2f %-100v \n", hop.TTL, "???", float32(100), '%', int(0), float32(0), float32(0), float32(0), float32(0), "null") 49 | } else { 50 | lastHop++ 51 | buffer.WriteString(fmt.Sprintf("%-3d %-48v\n", lastHop, "???")) 52 | } 53 | } 54 | } 55 | 56 | return buffer.String(), nil 57 | } 58 | 59 | func geoFor(ip string) string { 60 | country, city, err := common.GetIpInfo(ip) 61 | fmt.Println(111, ip, country, city, err) 62 | if err != nil { 63 | return "" 64 | } 65 | return fmt.Sprintf("%s:%s", country, city) 66 | } 67 | 68 | func getSafeIdent() int { 69 | thold := 1000 70 | gid := common.Goid() 71 | if gid < thold { 72 | return gid + thold 73 | } 74 | return gid 75 | } 76 | 77 | func batchDetect(destAddr string, options *MtrOptions) [][]*common.IcmpResp { 78 | var ( 79 | defaultSeq = 0 80 | resps = make([][]*common.IcmpResp, options.SntSize()) 81 | timeout = time.Duration(options.TimeoutMs()) * time.Millisecond 82 | 83 | lock sync.Mutex 84 | ) 85 | 86 | for idx := range resps { 87 | resps[idx] = make([]*common.IcmpResp, options.MaxHops()+1) 88 | } 89 | 90 | for snt := 0; snt < options.SntSize(); snt++ { 91 | wg := sync.WaitGroup{} 92 | for ttl := 1; ttl < options.MaxHops(); ttl++ { 93 | ttl := ttl // copy 94 | wg.Add(1) 95 | go func() { 96 | defer wg.Done() 97 | 98 | // avoid ping resp conflict when multi gorouting handle ping 99 | pid := getSafeIdent() 100 | data, err := icmp.Icmp(destAddr, ttl, pid, timeout, defaultSeq) 101 | if err != nil || !data.Success { 102 | spew.Infof("failed to ping icmp, err: %s", err.Error()) 103 | return 104 | } 105 | 106 | lock.Lock() 107 | resps[0][ttl] = &data 108 | lock.Unlock() 109 | }() 110 | } 111 | wg.Wait() 112 | } 113 | return resps 114 | } 115 | 116 | func runMtr(destAddr string, options *MtrOptions) (result MtrResult, err error) { 117 | result.Hops = []common.IcmpHop{} 118 | result.DestAddress = destAddr 119 | mtrResults := make([]*MtrReturn, options.MaxHops()+1) // not use first index 120 | 121 | multiResps := batchDetect(destAddr, options) 122 | for _, resps := range multiResps { 123 | for ttl := 1; ttl < options.MaxHops(); ttl++ { 124 | // init 125 | if mtrResults[ttl] == nil { 126 | mtrResults[ttl] = &MtrReturn{TTL: ttl, Host: "???", SuccSum: 0, Success: false, LastTime: time.Duration(0), AllTime: time.Duration(0), BestTime: time.Duration(0), WrstTime: time.Duration(0), AvgTime: time.Duration(0)} 127 | } 128 | 129 | // padding 130 | data := resps[ttl] 131 | if data == nil { 132 | continue 133 | } 134 | 135 | mtrResults[ttl].SuccSum = mtrResults[ttl].SuccSum + 1 136 | mtrResults[ttl].Host = data.Addr 137 | mtrResults[ttl].LastTime = data.Elapsed 138 | if mtrResults[ttl].WrstTime == time.Duration(0) || data.Elapsed > mtrResults[ttl].WrstTime { 139 | mtrResults[ttl].WrstTime = data.Elapsed 140 | } 141 | if mtrResults[ttl].BestTime == time.Duration(0) || data.Elapsed < mtrResults[ttl].BestTime { 142 | mtrResults[ttl].BestTime = data.Elapsed 143 | } 144 | mtrResults[ttl].AllTime += data.Elapsed 145 | mtrResults[ttl].AvgTime = time.Duration((int64)(mtrResults[ttl].AllTime/time.Microsecond)/(int64)(mtrResults[ttl].SuccSum)) * time.Microsecond 146 | mtrResults[ttl].Success = true 147 | 148 | if common.IsEqualIp(data.Addr, destAddr) { 149 | continue 150 | } 151 | } 152 | } 153 | 154 | // for snt := 0; snt < options.SntSize(); snt++ { 155 | // for ttl := 1; ttl < options.MaxHops(); ttl++ { 156 | // data, err := icmp.Icmp(destAddr, ttl, pid, timeout, seq) 157 | // if err != nil || !data.Success { 158 | // spew.Infof("failed to ping icmp, err: %s", err.Error()) 159 | // continue 160 | // } 161 | 162 | // // init 163 | // if mtrResults[ttl] == nil { 164 | // mtrResults[ttl] = &MtrReturn{TTL: ttl, Host: "???", SuccSum: 0, Success: false, LastTime: time.Duration(0), AllTime: time.Duration(0), BestTime: time.Duration(0), WrstTime: time.Duration(0), AvgTime: time.Duration(0)} 165 | // } 166 | 167 | // // padding 168 | // mtrResults[ttl].SuccSum = mtrResults[ttl].SuccSum + 1 169 | // mtrResults[ttl].Host = data.Addr 170 | // mtrResults[ttl].LastTime = data.Elapsed 171 | // if mtrResults[ttl].WrstTime == time.Duration(0) || data.Elapsed > mtrResults[ttl].WrstTime { 172 | // mtrResults[ttl].WrstTime = data.Elapsed 173 | // } 174 | // if mtrResults[ttl].BestTime == time.Duration(0) || data.Elapsed < mtrResults[ttl].BestTime { 175 | // mtrResults[ttl].BestTime = data.Elapsed 176 | // } 177 | // mtrResults[ttl].AllTime += data.Elapsed 178 | // mtrResults[ttl].AvgTime = time.Duration((int64)(mtrResults[ttl].AllTime/time.Microsecond)/(int64)(mtrResults[ttl].SuccSum)) * time.Microsecond 179 | // mtrResults[ttl].Success = true 180 | 181 | // if common.IsEqualIp(data.Addr, destAddr) { 182 | // break 183 | // } 184 | // } 185 | // } 186 | 187 | for index, mtrResult := range mtrResults { 188 | if index == 0 { 189 | continue 190 | } 191 | 192 | if mtrResult == nil { 193 | break 194 | } 195 | 196 | hop := common.IcmpHop{TTL: mtrResult.TTL, Snt: options.SntSize()} 197 | hop.Address = mtrResult.Host 198 | hop.Host = mtrResult.Host 199 | hop.AvgTime = mtrResult.AvgTime 200 | hop.BestTime = mtrResult.BestTime 201 | hop.LastTime = mtrResult.LastTime 202 | failSum := options.SntSize() - mtrResult.SuccSum 203 | loss := (float32)(failSum) / (float32)(options.SntSize()) * 100 204 | hop.Loss = float32(loss) 205 | hop.WrstTime = mtrResult.WrstTime 206 | hop.Success = mtrResult.Success 207 | 208 | result.Hops = append(result.Hops, hop) 209 | 210 | if common.IsEqualIp(hop.Host, destAddr) { 211 | break 212 | } 213 | } 214 | 215 | return result, nil 216 | } 217 | -------------------------------------------------------------------------------- /mtr/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 JD Inc. All Rights Reserved. 2 | // type.go - file brief introduce 3 | /* 4 | modification history 5 | ---------------------------------------------- 6 | 2019/5/26 0:20, by wangyulong3@jd.com, create 7 | 8 | */ 9 | /* 10 | Description 11 | 12 | */ 13 | 14 | package mtr 15 | 16 | import ( 17 | "time" 18 | 19 | "github.com/rfyiamcool/gomtr/common" 20 | ) 21 | 22 | const DEFAULT_MAX_HOPS = 30 23 | const DEFAULT_TIMEOUT_MS = 800 24 | const DEFAULT_PACKET_SIZE = 56 25 | const DEFAULT_SNT_SIZE = 5 26 | 27 | type MtrReturn struct { 28 | Success bool 29 | TTL int 30 | Host string 31 | SuccSum int 32 | LastTime time.Duration 33 | AllTime time.Duration 34 | BestTime time.Duration 35 | AvgTime time.Duration 36 | WrstTime time.Duration 37 | } 38 | 39 | type MtrResult struct { 40 | DestAddress string 41 | Hops []common.IcmpHop 42 | } 43 | 44 | type MtrOptions struct { 45 | maxHops int 46 | timeoutMs int 47 | packetSize int 48 | sntSize int 49 | } 50 | 51 | func (options *MtrOptions) MaxHops() int { 52 | if options.maxHops == 0 { 53 | options.maxHops = DEFAULT_MAX_HOPS 54 | } 55 | return options.maxHops 56 | } 57 | 58 | func (options *MtrOptions) SetMaxHops(maxHops int) { 59 | options.maxHops = maxHops 60 | } 61 | 62 | func (options *MtrOptions) TimeoutMs() int { 63 | if options.timeoutMs == 0 { 64 | options.timeoutMs = DEFAULT_TIMEOUT_MS 65 | } 66 | return options.timeoutMs 67 | } 68 | 69 | func (options *MtrOptions) SetTimeoutMs(timeoutMs int) { 70 | options.timeoutMs = timeoutMs 71 | } 72 | 73 | func (options *MtrOptions) SntSize() int { 74 | if options.sntSize == 0 { 75 | options.sntSize = DEFAULT_SNT_SIZE 76 | } 77 | return options.sntSize 78 | } 79 | 80 | func (options *MtrOptions) SetSntSize(sntSize int) { 81 | options.sntSize = sntSize 82 | } 83 | 84 | func (options *MtrOptions) PacketSize() int { 85 | if options.packetSize == 0 { 86 | options.packetSize = DEFAULT_PACKET_SIZE 87 | } 88 | return options.packetSize 89 | } 90 | 91 | func (options *MtrOptions) SetPacketSize(packetSize int) { 92 | options.packetSize = packetSize 93 | } 94 | -------------------------------------------------------------------------------- /ping/ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/rfyiamcool/gomtr/common" 9 | "github.com/rfyiamcool/gomtr/icmp" 10 | "github.com/rfyiamcool/gomtr/spew" 11 | ) 12 | 13 | func Ping(addr string, count, timeout, interval int) (result string, err error) { 14 | pingOptions := &PingOptions{} 15 | pingOptions.SetCount(count) 16 | pingOptions.SetTimeoutMs(timeout) 17 | pingOptions.SetIntervalMs(interval) 18 | 19 | ipAddrs, err := common.LookupIps(addr) 20 | if err != nil || len(ipAddrs) == 0 { 21 | return 22 | } 23 | 24 | var buffer bytes.Buffer 25 | buffer.WriteString(fmt.Sprintf("Start %v, PING %v (%v)\n", time.Now().Format("2006-01-02 15:04:05"), addr, ipAddrs[0])) 26 | begin := time.Now().UnixNano() / 1e6 27 | pingResp := runPing(ipAddrs[0], pingOptions) 28 | end := time.Now().UnixNano() / 1e6 29 | 30 | buffer.WriteString(fmt.Sprintf("%v packets transmitted, %v packet loss, time %vms\n", count, pingResp.dropRate, end-begin)) 31 | buffer.WriteString(fmt.Sprintf("rtt min/avg/max = %v/%v/%v ms\n", common.Time2Float(pingResp.wrstTime), common.Time2Float(pingResp.avgTime), common.Time2Float(pingResp.bestTime))) 32 | 33 | buffer.WriteString(fmt.Sprintf("rtt min/avg/max = %v/%v/%v ms\n", pingResp.wrstTime.String(), pingResp.avgTime.String(), pingResp.bestTime.String())) 34 | 35 | result = buffer.String() 36 | 37 | return 38 | } 39 | 40 | func runPing(ipaddr string, option *PingOptions) (pingResp PingResp) { 41 | pingResp = PingResp{} 42 | pingResp.destAddr = ipaddr 43 | 44 | var ( 45 | pid = common.Goid() 46 | timeout = time.Duration(option.TimeoutMs()) * time.Millisecond 47 | interval = option.IntervalMs() 48 | pingResult = PingResult{} 49 | ) 50 | 51 | seq := 0 52 | for cnt := 0; cnt < option.Count(); cnt++ { 53 | icmprt, err := icmp.Icmp(ipaddr, DEFAULT_TTL, pid, timeout, seq) 54 | if err != nil || !icmprt.Success || !common.IsEqualIp(ipaddr, icmprt.Addr) { 55 | spew.Errorf("failed to ping addr %s, err: %v", ipaddr, err) 56 | continue 57 | } 58 | 59 | pingResult.succSum++ 60 | if pingResult.wrstTime == time.Duration(0) || icmprt.Elapsed > pingResult.wrstTime { 61 | pingResult.wrstTime = icmprt.Elapsed 62 | } 63 | if pingResult.bestTime == time.Duration(0) || icmprt.Elapsed < pingResult.bestTime { 64 | pingResult.bestTime = icmprt.Elapsed 65 | } 66 | pingResult.allTime += icmprt.Elapsed 67 | pingResult.avgTime = time.Duration((int64)(pingResult.allTime/time.Microsecond)/(int64)(pingResult.succSum)) * time.Microsecond 68 | pingResult.success = true 69 | 70 | seq++ 71 | 72 | time.Sleep(time.Duration(interval) * time.Millisecond) 73 | } 74 | 75 | if !pingResult.success { 76 | pingResp.success = false 77 | pingResp.dropRate = 100.0 78 | return 79 | } 80 | 81 | pingResp.success = pingResult.success 82 | pingResp.dropRate = float64(option.Count()-pingResult.succSum) / float64(option.Count()) 83 | pingResp.avgTime = pingResult.avgTime 84 | pingResp.bestTime = pingResult.bestTime 85 | pingResp.wrstTime = pingResult.wrstTime 86 | 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /ping/types.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import "time" 4 | 5 | const ( 6 | DEFAULT_TIMEOUT_MS = 1000 // 1000ms = 1s 7 | DEFAULT_PACKET_SIZE = 56 8 | DEFAULT_COUNT = 10 9 | DEFAULT_INTERVAL_MS = 10 10 | DEFAULT_TTL = 128 11 | ) 12 | 13 | type PingResp struct { 14 | destAddr string 15 | success bool 16 | dropRate float64 17 | allTime time.Duration 18 | bestTime time.Duration 19 | avgTime time.Duration 20 | wrstTime time.Duration 21 | } 22 | 23 | type PingResult struct { 24 | success bool 25 | succSum int 26 | allTime time.Duration 27 | bestTime time.Duration 28 | avgTime time.Duration 29 | wrstTime time.Duration 30 | } 31 | 32 | type PingOptions struct { 33 | count int 34 | timeoutMs int 35 | intervalMs int 36 | packetSize int 37 | } 38 | 39 | func (options *PingOptions) Count() int { 40 | if options.count == 0 { 41 | options.count = DEFAULT_COUNT 42 | } 43 | return options.count 44 | } 45 | 46 | func (options *PingOptions) SetCount(count int) { 47 | options.count = count 48 | } 49 | 50 | func (options *PingOptions) TimeoutMs() int { 51 | if options.timeoutMs == 0 { 52 | options.timeoutMs = DEFAULT_TIMEOUT_MS 53 | } 54 | return options.timeoutMs 55 | } 56 | 57 | func (options *PingOptions) SetTimeoutMs(timeoutMs int) { 58 | options.timeoutMs = timeoutMs 59 | } 60 | 61 | func (options *PingOptions) IntervalMs() int { 62 | if options.intervalMs == 0 { 63 | options.intervalMs = DEFAULT_INTERVAL_MS 64 | } 65 | return options.intervalMs 66 | } 67 | 68 | func (options *PingOptions) SetIntervalMs(intervalMs int) { 69 | options.intervalMs = intervalMs 70 | } 71 | 72 | func (options *PingOptions) PacketSize() int { 73 | if options.packetSize == 0 { 74 | options.packetSize = DEFAULT_PACKET_SIZE 75 | } 76 | return options.packetSize 77 | } 78 | 79 | func (options *PingOptions) SetPacketSize(packetSize int) { 80 | options.packetSize = packetSize 81 | } 82 | -------------------------------------------------------------------------------- /qqwry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfyiamcool/gomtr/95d50030c33fac84349538a2bc3afdf2a37e7479/qqwry.dat -------------------------------------------------------------------------------- /spew/spew.go: -------------------------------------------------------------------------------- 1 | package spew 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/davecgh/go-spew/spew" 9 | "github.com/fatih/color" 10 | ) 11 | 12 | // usage set fg color and bg color 13 | // red := color.New(color.FgRed).Add(color.BgGreen) 14 | // red.Println("xiaorui.cc") 15 | 16 | func Open() { 17 | color.Set(color.FgMagenta, color.Bold) 18 | } 19 | 20 | func Close() { 21 | defer color.Unset() 22 | } 23 | 24 | // usage set fg color and bg color 25 | // red := color.New(color.FgRed).Add(color.BgGreen) 26 | // red.Println("xiaorui.cc") 27 | 28 | func Panic(v ...interface{}) { 29 | red := color.New(color.FgBlack).Add(color.BgRed) 30 | red.Println(v...) 31 | } 32 | 33 | func Error(v ...interface{}) { 34 | s := fmt.Sprintln(v...) 35 | color.Red(s) 36 | } 37 | 38 | func Errorf(format string, v ...interface{}) { 39 | color.Red(format, v...) 40 | } 41 | 42 | func Warn(v ...interface{}) { 43 | s := fmt.Sprintln(v...) 44 | color.Yellow(s) 45 | } 46 | 47 | func Warnf(format string, v ...interface{}) { 48 | color.Yellow(format, v...) 49 | } 50 | 51 | func Info(v ...interface{}) { 52 | s := fmt.Sprintln(v...) 53 | color.Blue(s) 54 | } 55 | 56 | func Infof(format string, v ...interface{}) { 57 | color.Blue(format, v...) 58 | } 59 | 60 | func Debug(v ...interface{}) { 61 | s := fmt.Sprintln(v...) 62 | color.Green(s) 63 | } 64 | 65 | func Debugf(format string, v ...interface{}) { 66 | color.Green(format, v...) 67 | } 68 | 69 | func Dump(v ...interface{}) { 70 | s := spew.Sdump(v...) 71 | file, no, funcName := getCaller(2) 72 | color.Magenta("file: %s line: %d, funcname: %s message: %s", file, no, funcName, s) 73 | } 74 | 75 | func getCaller(skip int) (string, int, string) { 76 | pc, file, line, ok := runtime.Caller(skip) 77 | if !ok { 78 | return "", 0, "" 79 | } 80 | 81 | var ( 82 | n = 0 83 | funcName string 84 | ) 85 | 86 | // get package name 87 | for i := len(file) - 1; i > 0; i-- { 88 | if file[i] != '/' { 89 | continue 90 | } 91 | n++ 92 | if n >= 2 { 93 | file = file[i+1:] 94 | break 95 | } 96 | } 97 | 98 | fnpc := runtime.FuncForPC(pc) 99 | 100 | if fnpc != nil { 101 | fnNameStr := fnpc.Name() 102 | parts := strings.Split(fnNameStr, ".") 103 | funcName = parts[len(parts)-1] 104 | } 105 | 106 | return file, line, funcName 107 | } 108 | --------------------------------------------------------------------------------