├── go.mod ├── .gitignore ├── examples ├── simple │ └── simple.go ├── callback │ └── callback.go ├── multi │ └── multi.go ├── parallel │ └── parallel.go ├── mparallel │ └── mparallel.go └── json │ └── json.go ├── go.sum ├── LICENSE ├── README.md ├── tools.go ├── tracelib.go └── ptrace.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kanocz/tracelib 2 | 3 | go 1.14 4 | 5 | require golang.org/x/net v0.0.0-20200707034311-ab3426394381 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .vscode 27 | -------------------------------------------------------------------------------- /examples/simple/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kanocz/tracelib" 8 | ) 9 | 10 | func main() { 11 | cache := tracelib.NewLookupCache() 12 | 13 | hops, err := tracelib.RunTrace("google.com", "0.0.0.0", "::", time.Second, 64, cache, nil) 14 | 15 | if nil != err { 16 | fmt.Println("Traceroute error:", err) 17 | return 18 | } 19 | 20 | for i, hop := range hops { 21 | fmt.Printf("%d. %v(%s)/AS%d %v (final:%v timeout:%v error:%v down:%v)\n", i+1, hop.Host, hop.Addr, hop.AS, hop.RTT, hop.Final, hop.Timeout, hop.Error, hop.Down) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/callback/callback.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kanocz/tracelib" 8 | ) 9 | 10 | func printStep(hop tracelib.Hop, num int, round int) { 11 | fmt.Printf("%d.(%d) %v(%s)/AS%d %v (final:%v timeout:%v error:%v down:%v)\n", num, round, hop.Host, hop.Addr, hop.AS, hop.RTT, hop.Final, hop.Timeout, hop.Error, hop.Down) 12 | } 13 | 14 | func main() { 15 | cache := tracelib.NewLookupCache() 16 | 17 | fmt.Println("Single round trace") 18 | _, err := tracelib.RunTrace("google.com", "0.0.0.0", "::", time.Second, 64, cache, printStep) 19 | 20 | if nil != err { 21 | fmt.Println("Traceroute error:", err) 22 | return 23 | } 24 | 25 | fmt.Println("Multi round trace") 26 | _, err = tracelib.RunMultiTrace("google.com", "0.0.0.0", "::", time.Second, 64, cache, 3, printStep) 27 | 28 | if nil != err { 29 | fmt.Println("Traceroute error:", err) 30 | return 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /examples/multi/multi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "strings" 8 | 9 | "github.com/kanocz/tracelib" 10 | ) 11 | 12 | func main() { 13 | cache := tracelib.NewLookupCache() 14 | 15 | rawHops, err := tracelib.RunMultiTrace("homebeat.live", "0.0.0.0", "::", time.Second, 64, cache, 10, nil) 16 | 17 | if nil != err { 18 | fmt.Println("Traceroute error:", err) 19 | return 20 | } 21 | 22 | hops := tracelib.AggregateMulti(rawHops) 23 | 24 | for i, hop := range hops { 25 | isd := fmt.Sprintf("%d. ", i+1) 26 | isp := strings.Repeat(" ", len(isd)) 27 | 28 | for j, h := range hop { 29 | prefix := isd 30 | if j > 0 { 31 | prefix = isp 32 | } 33 | 34 | if nil != h.Addr { 35 | fmt.Printf("%s%v(%s)/AS%d %v/%v/%v (final:%v lost %d of %d, down %d of %d)\n", prefix, h.Host, h.Addr, h.AS, h.MinRTT, h.AvgRTT, h.MaxRTT, h.Final, h.Lost, h.Total, h.Down, h.Total) 36 | } else { 37 | fmt.Printf("%s Lost: %d\n", prefix, h.Lost) 38 | } 39 | 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 3 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 4 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= 5 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 6 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 7 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 8 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 9 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 10 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Anton Skorochod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/parallel/parallel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kanocz/tracelib" 9 | ) 10 | 11 | func main() { 12 | cache := tracelib.NewLookupCache() 13 | 14 | rawHops, err := tracelib.RunPTrace("ttc.ca", "0.0.0.0", "::", time.Second, 32, cache, 10, 100, time.Millisecond) 15 | // rawHops, err := tracelib.RunPTrace("homebeat.live", "0.0.0.0", "::", time.Second, 32, cache, 10, 100, 0, time.Millisecond) 16 | 17 | if nil != err { 18 | fmt.Println("Traceroute error:", err) 19 | return 20 | } 21 | 22 | // fmt.Printf("%+v\n", rawHops) 23 | 24 | hops := tracelib.AggregateMulti(rawHops) 25 | 26 | for i, hop := range hops { 27 | isd := fmt.Sprintf("%d. ", i+1) 28 | isp := strings.Repeat(" ", len(isd)) 29 | 30 | for j, h := range hop { 31 | prefix := isd 32 | if j > 0 { 33 | prefix = isp 34 | } 35 | 36 | if nil != h.Addr { 37 | fmt.Printf("%s%v(%s)/AS%d %v/%v/%v (final:%v lost %d of %d, down %d of %d)\n", prefix, h.Host, h.Addr, h.AS, h.MinRTT, h.AvgRTT, h.MaxRTT, h.Final, h.Lost, h.Total, h.Down, h.Total) 38 | } else { 39 | fmt.Printf("%s Lost: %d\n", prefix, h.Lost) 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/mparallel/mparallel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kanocz/tracelib" 9 | ) 10 | 11 | func main() { 12 | cache := tracelib.NewLookupCache() 13 | 14 | rawMHops, err := tracelib.RunMPTrace([]string{"homebeat.live", "cocopacket.com", "skorochod.cz"}, "0.0.0.0", "::", time.Second, 32, cache, 10, 100, time.Millisecond) 15 | 16 | if nil != err { 17 | fmt.Println("Traceroute error:", err) 18 | return 19 | } 20 | 21 | // fmt.Printf("%+v\n", rawHops) 22 | 23 | for host, rawHops := range rawMHops { 24 | 25 | fmt.Println("trace to ", host) 26 | 27 | hops := tracelib.AggregateMulti(*rawHops) 28 | 29 | for i, hop := range hops { 30 | isd := fmt.Sprintf("%d. ", i+1) 31 | isp := strings.Repeat(" ", len(isd)) 32 | 33 | for j, h := range hop { 34 | prefix := isd 35 | if j > 0 { 36 | prefix = isp 37 | } 38 | 39 | if nil != h.Addr { 40 | fmt.Printf("%s%v(%s)/AS%d %v/%v/%v (final:%v lost %d of %d, down %d of %d)\n", prefix, h.Host, h.Addr, h.AS, h.MinRTT, h.AvgRTT, h.MaxRTT, h.Final, h.Lost, h.Total, h.Down, h.Total) 41 | } else { 42 | fmt.Printf("%s Lost: %d\n", prefix, h.Lost) 43 | } 44 | 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tracelib 2 | Traceroute implementation in go including mutli-round trace (returns min/max/avg/lost) and AS number detection both for IPv4 and IPv6. Also expremental implementation of much faster traceroute present (it sends all packets with all possible TTLs at once and total tracroute time is always the same as MaxRTT), look at examples/parallel for more info. 3 | 4 | Usage example of regular traceroute (only IPs without hostnames and AS numbers): 5 | ```go 6 | hops, err := tracelib.RunTrace("google.com", "0.0.0.0", time.Second, 64, nil) 7 | for i, hop := range hops { 8 | fmt.Printf("%d. %v(%s)/AS%d %v (final:%v timeout:%v error:%v)\n", 9 | i+1, hop.Host, hop.Addr, hop.AS, hop.RTT, hop.Final, hop.Timeout, hop.Error) 10 | } 11 | ``` 12 | 13 | Multiply traces with hostnames and AS numbers: 14 | ```go 15 | dnscache := tracelib.NewLookupCache() 16 | rawHops, err := tracelib.RunMultiTrace("homebeat.live", "0.0.0.0", time.Second, 64, dnscache, 5) 17 | 18 | hops := tracelib.AggregateMulti(rawHops) 19 | 20 | for i, hop := range hops { 21 | isd := fmt.Sprintf("%d. ", i+1) 22 | isp := strings.Repeat(" ", len(isd)) 23 | 24 | for j, h := range hop { 25 | prefix := isd 26 | if j > 0 { prefix = isp } 27 | 28 | fmt.Printf("%s%v(%s)/AS%d %v/%v/%v (final:%v lost %d of %d)\n", 29 | prefix, h.Host, h.Addr, h.AS, h.MinRTT, h.AvgRTT, h.MaxRTT, h.Final, h.Lost, h.Total) 30 | } 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /examples/json/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/kanocz/tracelib" 10 | ) 11 | 12 | type mtrhHost struct { 13 | IP string `json:"ip"` 14 | Host string `json:"host"` 15 | AS int64 `json:"as"` 16 | Min string `json:"min"` 17 | Avg string `json:"avg"` 18 | Max string `json:"max"` 19 | Rcvd int `json:"received"` 20 | } 21 | 22 | func doTrace(host string) ([]byte, error) { 23 | cache := tracelib.NewLookupCache() 24 | 25 | rawHops, err := tracelib.RunMultiTrace(host, "0.0.0.0", "::", time.Second, 64, cache, 10, nil) 26 | if nil != err { 27 | return nil, err 28 | } 29 | hops := tracelib.AggregateMulti(rawHops) 30 | 31 | result := make([][]mtrhHost, 0, len(hops)) 32 | 33 | for _, hop := range hops { 34 | nextSlice := make([]mtrhHost, 0, len(hop)) 35 | 36 | for _, h := range hop { 37 | if nil == h.Addr { 38 | continue 39 | } 40 | next := mtrhHost{} 41 | next.AS = h.AS 42 | next.IP = h.Addr.String() 43 | next.Host = h.Host 44 | next.Avg = fmt.Sprintf("%.2f", float64(h.AvgRTT)/float64(time.Millisecond)) 45 | next.Max = fmt.Sprintf("%.2f", float64(h.MaxRTT)/float64(time.Millisecond)) 46 | next.Min = fmt.Sprintf("%.2f", float64(h.MinRTT)/float64(time.Millisecond)) 47 | if h.Total == 0 { 48 | next.Rcvd = 0 49 | } else { 50 | next.Rcvd = (100 * h.Total) / 10 51 | } 52 | nextSlice = append(nextSlice, next) 53 | } 54 | 55 | result = append(result, nextSlice) 56 | } 57 | 58 | out, err := json.MarshalIndent(result, "", " ") 59 | if nil != err { 60 | return nil, err 61 | } 62 | 63 | return out, nil 64 | } 65 | 66 | func main() { 67 | 68 | j, err := doTrace("homebeat.live") 69 | 70 | if nil != err { 71 | log.Fatalln("Error: ", err) 72 | } 73 | 74 | fmt.Println(string(j)) 75 | } 76 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | package tracelib 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // MHop represents aggregated result of hop of multiply traces 14 | type MHop struct { 15 | Addr net.Addr 16 | Host string 17 | AS int64 18 | MinRTT time.Duration 19 | MaxRTT time.Duration 20 | AvgRTT time.Duration 21 | Total int 22 | Lost int 23 | Down int 24 | Final bool 25 | } 26 | 27 | // AggregateMulti process result of RunMultiTrace and create aggregated result 28 | func AggregateMulti(hops [][]Hop) [][]MHop { 29 | 30 | result := make([][]MHop, 0, len(hops)) 31 | 32 | for _, hop := range hops { 33 | thishop := map[string]MHop{} 34 | 35 | timesum := map[string]time.Duration{} 36 | 37 | for _, h := range hop { 38 | var addrstring string 39 | if nil != h.Addr { 40 | addrstring = h.Addr.String() 41 | } 42 | 43 | var ( 44 | mhop MHop 45 | ok bool 46 | ) 47 | 48 | if mhop, ok = thishop[addrstring]; !ok { 49 | mhop.Addr = h.Addr 50 | mhop.Host = h.Host 51 | mhop.AS = h.AS 52 | mhop.Final = h.Final 53 | timesum[addrstring] = 0 54 | } 55 | 56 | mhop.Total++ 57 | 58 | switch { 59 | case h.Down: 60 | mhop.Down++ 61 | case h.Timeout || nil != h.Error: 62 | mhop.Lost++ 63 | default: 64 | timesum[addrstring] += h.RTT 65 | if mhop.MaxRTT < h.RTT { 66 | mhop.MaxRTT = h.RTT 67 | } 68 | if mhop.MinRTT == 0 || mhop.MinRTT > h.RTT { 69 | mhop.MinRTT = h.RTT 70 | } 71 | } 72 | 73 | mhop.Final = mhop.Final || h.Final 74 | if mhop.Total > mhop.Lost { 75 | mhop.AvgRTT = timesum[addrstring] / time.Duration(mhop.Total-mhop.Lost) 76 | } 77 | thishop[addrstring] = mhop 78 | } 79 | 80 | thisSlice := make([]MHop, 0, len(thishop)) 81 | for _, h := range thishop { 82 | thisSlice = append(thisSlice, h) 83 | } 84 | 85 | result = append(result, thisSlice) 86 | } 87 | 88 | return result 89 | 90 | } 91 | 92 | // LookupCache used to prevent AS-DNS requests for same hosts 93 | type LookupCache struct { 94 | as map[string]int64 95 | aMutex sync.RWMutex 96 | hosts map[string]string 97 | hMutex sync.RWMutex 98 | } 99 | 100 | // NewLookupCache constructor for LookupCache 101 | func NewLookupCache() *LookupCache { 102 | return &LookupCache{ 103 | as: make(map[string]int64, 1024), 104 | hosts: make(map[string]string, 4096), 105 | } 106 | } 107 | 108 | // LookupAS returns AS number for IP using origin.asn.cymru.com service 109 | func (cache *LookupCache) LookupAS(ip string) int64 { 110 | cache.aMutex.RLock() 111 | v, exist := cache.as[ip] 112 | cache.aMutex.RUnlock() 113 | if exist { 114 | return v 115 | } 116 | 117 | ipParts := strings.Split(ip, ".") 118 | if len(ipParts) == 4 { 119 | return cache.lookupAS4(ip, ipParts) 120 | } 121 | 122 | return cache.lookupAS6(ip) 123 | } 124 | 125 | func (cache *LookupCache) lookupAS4(ip string, ipParts []string) int64 { 126 | 127 | txts, err := net.LookupTXT(fmt.Sprintf("%s.%s.%s.%s.origin.asn.cymru.com", ipParts[3], ipParts[2], ipParts[1], ipParts[0])) 128 | if nil != err || nil == txts || len(txts) < 1 { 129 | return -1 130 | } 131 | 132 | parts := strings.Split(txts[0], " | ") 133 | if len(parts) < 2 { 134 | return -1 135 | } 136 | 137 | asnum, err := strconv.ParseInt(parts[0], 10, 64) 138 | if nil != err { 139 | return -1 140 | } 141 | 142 | cache.aMutex.Lock() 143 | cache.as[ip] = asnum 144 | cache.aMutex.Unlock() 145 | 146 | return asnum 147 | } 148 | 149 | func (cache *LookupCache) lookupAS6(ip string) int64 { 150 | 151 | i6 := net.ParseIP(ip) 152 | if len(i6) != 16 { 153 | return -1 154 | } 155 | 156 | hexIP := "" 157 | for _, v := range hex.EncodeToString([]byte(i6)) { 158 | hexIP = string(v) + "." + hexIP 159 | } 160 | 161 | txts, err := net.LookupTXT(hexIP + "origin6.asn.cymru.com") 162 | if nil != err || nil == txts || len(txts) < 1 { 163 | return -1 164 | } 165 | 166 | parts := strings.Split(txts[0], " | ") 167 | if len(parts) < 2 { 168 | return -1 169 | } 170 | 171 | asnum, err := strconv.ParseInt(parts[0], 10, 64) 172 | if nil != err { 173 | return -1 174 | } 175 | 176 | cache.aMutex.Lock() 177 | cache.as[ip] = asnum 178 | cache.aMutex.Unlock() 179 | 180 | return asnum 181 | } 182 | 183 | // LookupHost returns AS number for IP using origin.asn.cymru.com service 184 | func (cache *LookupCache) LookupHost(ip string) string { 185 | cache.hMutex.RLock() 186 | v, exist := cache.hosts[ip] 187 | cache.hMutex.RUnlock() 188 | if exist { 189 | return v 190 | } 191 | 192 | var result string 193 | 194 | addrs, _ := net.LookupAddr(ip) 195 | if len(addrs) > 0 { 196 | result = addrs[0] 197 | } 198 | 199 | cache.hMutex.Lock() 200 | cache.hosts[ip] = result 201 | cache.hMutex.Unlock() 202 | 203 | return result 204 | } 205 | -------------------------------------------------------------------------------- /tracelib.go: -------------------------------------------------------------------------------- 1 | package tracelib 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "math/rand" 7 | "net" 8 | "time" 9 | 10 | "golang.org/x/net/icmp" 11 | "golang.org/x/net/ipv4" 12 | "golang.org/x/net/ipv6" 13 | ) 14 | 15 | const ( 16 | // ProtocolICMP icmp protocol id 17 | ProtocolICMP = 1 18 | // ProtocolICMP6 icmp protocol id 19 | ProtocolICMP6 = 58 20 | 21 | // MaxTimeouts sets number of hops without replay before trace termination 22 | MaxTimeouts = 3 23 | ) 24 | 25 | // trace struct represents handles connections and info for trace 26 | type trace struct { 27 | conn net.PacketConn 28 | ipv4conn *ipv4.PacketConn 29 | ipv6conn *ipv6.PacketConn 30 | msg icmp.Message 31 | netmsg []byte 32 | id int 33 | maxrtt time.Duration 34 | maxttl int 35 | dest net.Addr 36 | } 37 | 38 | // Callback function called after every hop received 39 | type Callback func(info Hop, hopnum int, round int) 40 | 41 | // RunTrace preforms traceroute to specified host 42 | func RunTrace(host string, source string, source6 string, maxrtt time.Duration, maxttl int, DNScache *LookupCache, cb Callback) ([]Hop, error) { 43 | hops := make([]Hop, 0, maxttl) 44 | 45 | var res trace 46 | var err error 47 | isIPv6 := false 48 | 49 | addrList, err := net.LookupIP(host) 50 | if nil != err { 51 | return nil, err 52 | } 53 | 54 | for _, addr := range addrList { 55 | if addr.To4() != nil { 56 | res.dest, err = net.ResolveIPAddr("ip4:icmp", addr.String()) 57 | break 58 | } 59 | } 60 | 61 | if nil == res.dest { 62 | for _, addr := range addrList { 63 | if addr.To16() != nil { 64 | isIPv6 = true 65 | res.dest, err = net.ResolveIPAddr("ip6:58", addr.String()) 66 | break 67 | } 68 | } 69 | } 70 | if nil == res.dest { 71 | return nil, errors.New("Unable to resolve destination host") 72 | } 73 | 74 | res.maxrtt = maxrtt 75 | res.maxttl = maxttl 76 | res.id = rand.Int() % 0x7fff 77 | if isIPv6 { 78 | res.msg = icmp.Message{Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &icmp.Echo{ID: res.id, Seq: 1}} 79 | } else { 80 | res.msg = icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: res.id, Seq: 1}} 81 | } 82 | res.netmsg, err = res.msg.Marshal(nil) 83 | 84 | if nil != err { 85 | return nil, err 86 | } 87 | 88 | if !isIPv6 { 89 | res.conn, err = net.ListenPacket("ip4:icmp", source) 90 | } else { 91 | res.conn, err = net.ListenPacket("ip6:58", source6) 92 | } 93 | if nil != err { 94 | return nil, err 95 | } 96 | defer res.conn.Close() 97 | 98 | if isIPv6 { 99 | res.ipv6conn = ipv6.NewPacketConn(res.conn) 100 | defer res.ipv6conn.Close() 101 | if err := res.ipv6conn.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { 102 | return nil, err 103 | } 104 | var f ipv6.ICMPFilter 105 | f.SetAll(true) 106 | f.Accept(ipv6.ICMPTypeTimeExceeded) 107 | f.Accept(ipv6.ICMPTypeEchoReply) 108 | f.Accept(ipv6.ICMPTypeDestinationUnreachable) 109 | if err := res.ipv6conn.SetICMPFilter(&f); err != nil { 110 | return nil, err 111 | } 112 | } else { 113 | res.ipv4conn = ipv4.NewPacketConn(res.conn) 114 | defer res.ipv4conn.Close() 115 | } 116 | 117 | timeouts := 0 118 | for i := 1; i <= maxttl; i++ { 119 | next := res.Step(i) 120 | if nil != next.Addr { 121 | addrString := next.Addr.String() 122 | if nil != DNScache { 123 | next.Host = DNScache.LookupHost(addrString) 124 | next.AS = DNScache.LookupAS(addrString) 125 | } 126 | } 127 | if nil != cb { 128 | cb(next, i, 1) 129 | } 130 | hops = append(hops, next) 131 | if next.Final { 132 | break 133 | } 134 | if next.Timeout { 135 | timeouts++ 136 | } else { 137 | timeouts = 0 138 | } 139 | if timeouts == MaxTimeouts { 140 | break 141 | } 142 | } 143 | 144 | return hops, nil 145 | } 146 | 147 | // RunMultiTrace preforms traceroute to specified host testing each hop several times 148 | func RunMultiTrace(host string, source string, source6 string, maxrtt time.Duration, maxttl int, DNScache *LookupCache, rounds int, cb Callback) ([][]Hop, error) { 149 | hops := make([][]Hop, 0, maxttl) 150 | 151 | var res trace 152 | var err error 153 | isIPv6 := false 154 | 155 | addrList, err := net.LookupIP(host) 156 | if nil != err { 157 | return nil, err 158 | } 159 | 160 | for _, addr := range addrList { 161 | if addr.To4() != nil { 162 | res.dest, err = net.ResolveIPAddr("ip4:icmp", addr.String()) 163 | break 164 | } 165 | } 166 | 167 | if nil == res.dest { 168 | for _, addr := range addrList { 169 | if addr.To16() != nil { 170 | isIPv6 = true 171 | res.dest, err = net.ResolveIPAddr("ip6:58", addr.String()) 172 | break 173 | } 174 | } 175 | } 176 | if nil == res.dest { 177 | return nil, errors.New("Unable to resolve destination host") 178 | } 179 | 180 | res.maxrtt = maxrtt 181 | res.maxttl = maxttl 182 | res.id = rand.Int() % 0x7fff 183 | if isIPv6 { 184 | res.msg = icmp.Message{Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &icmp.Echo{ID: res.id, Seq: 1}} 185 | } else { 186 | res.msg = icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: res.id, Seq: 1}} 187 | } 188 | res.netmsg, err = res.msg.Marshal(nil) 189 | 190 | if nil != err { 191 | return nil, err 192 | } 193 | 194 | if !isIPv6 { 195 | res.conn, err = net.ListenPacket("ip4:icmp", source) 196 | } else { 197 | res.conn, err = net.ListenPacket("ip6:58", source6) 198 | } 199 | if nil != err { 200 | return nil, err 201 | } 202 | defer res.conn.Close() 203 | 204 | if isIPv6 { 205 | res.ipv6conn = ipv6.NewPacketConn(res.conn) 206 | defer res.ipv6conn.Close() 207 | if err := res.ipv6conn.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { 208 | return nil, err 209 | } 210 | // var f ipv6.ICMPFilter 211 | // f.SetAll(true) 212 | // f.Accept(ipv6.ICMPTypeTimeExceeded) 213 | // f.Accept(ipv6.ICMPTypeEchoReply) 214 | // if err := res.ipv6conn.SetICMPFilter(&f); err != nil { 215 | // return nil, err 216 | // } 217 | } else { 218 | res.ipv4conn = ipv4.NewPacketConn(res.conn) 219 | defer res.ipv4conn.Close() 220 | } 221 | 222 | timeouts := 0 223 | for i := 1; i <= maxttl; i++ { 224 | thisHops := make([]Hop, 0, rounds) 225 | isFinal := false 226 | 227 | notimeout := true 228 | for j := 0; j < rounds; j++ { 229 | next := res.Step(i) 230 | if nil != next.Addr { 231 | addrString := next.Addr.String() 232 | if nil != DNScache { 233 | next.Host = DNScache.LookupHost(addrString) 234 | next.AS = DNScache.LookupAS(addrString) 235 | } 236 | } 237 | if nil != cb { 238 | cb(next, i, j+1) 239 | } 240 | thisHops = append(thisHops, next) 241 | isFinal = next.Final || isFinal 242 | notimeout = notimeout && (!next.Timeout) 243 | } 244 | hops = append(hops, thisHops) 245 | if isFinal { 246 | break 247 | } 248 | if notimeout { 249 | timeouts = 0 250 | } else { 251 | timeouts++ 252 | } 253 | 254 | if timeouts == MaxTimeouts { 255 | break 256 | } 257 | } 258 | 259 | return hops, nil 260 | } 261 | 262 | // Hop represents each hop of trace 263 | type Hop struct { 264 | Addr net.Addr 265 | Host string 266 | AS int64 267 | RTT time.Duration 268 | Final bool 269 | Timeout bool 270 | Down bool 271 | Error error 272 | } 273 | 274 | // Step sends one echo packet and waits for result 275 | func (t *trace) Step(ttl int) Hop { 276 | var hop Hop 277 | var wcm ipv6.ControlMessage 278 | 279 | hop.Error = t.conn.SetReadDeadline(time.Now().Add(t.maxrtt)) 280 | 281 | if nil != hop.Error { 282 | return hop 283 | } 284 | 285 | if nil != t.ipv4conn { 286 | hop.Error = t.ipv4conn.SetTTL(ttl) 287 | } 288 | if nil != t.ipv6conn { 289 | wcm.HopLimit = ttl 290 | } 291 | if nil != hop.Error { 292 | return hop 293 | } 294 | 295 | sendOn := time.Now() 296 | 297 | if nil != t.ipv4conn { 298 | _, hop.Error = t.conn.WriteTo(t.netmsg, t.dest) 299 | } 300 | if nil != t.ipv6conn { 301 | _, hop.Error = t.ipv6conn.WriteTo(t.netmsg, &wcm, t.dest) 302 | } 303 | if nil != hop.Error { 304 | return hop 305 | } 306 | 307 | buf := make([]byte, 1500) 308 | 309 | for { 310 | var readLen int 311 | 312 | readLen, hop.Addr, hop.Error = t.conn.ReadFrom(buf) 313 | 314 | if nerr, ok := hop.Error.(net.Error); ok && nerr.Timeout() { 315 | hop.Timeout = true 316 | return hop 317 | } 318 | 319 | if nil != hop.Error { 320 | return hop 321 | } 322 | 323 | var result *icmp.Message 324 | if nil != t.ipv4conn { 325 | result, hop.Error = icmp.ParseMessage(ProtocolICMP, buf[:readLen]) 326 | } 327 | if nil != t.ipv6conn { 328 | result, hop.Error = icmp.ParseMessage(ProtocolICMP6, buf[:readLen]) 329 | } 330 | if nil != hop.Error { 331 | return hop 332 | } 333 | 334 | hop.RTT = time.Since(sendOn) 335 | 336 | switch result.Type { 337 | case ipv4.ICMPTypeEchoReply: 338 | if rply, ok := result.Body.(*icmp.Echo); ok { 339 | if t.id != rply.ID { 340 | continue 341 | } 342 | hop.Final = true 343 | return hop 344 | } 345 | case ipv4.ICMPTypeTimeExceeded: 346 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 347 | if len(rply.Data) > 24 { 348 | if uint16(t.id) != binary.BigEndian.Uint16(rply.Data[24:26]) { 349 | continue 350 | } 351 | return hop 352 | } 353 | } 354 | case ipv6.ICMPTypeTimeExceeded: 355 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 356 | if len(rply.Data) > 44 { 357 | if uint16(t.id) != binary.BigEndian.Uint16(rply.Data[44:46]) { 358 | continue 359 | } 360 | return hop 361 | } 362 | } 363 | case ipv6.ICMPTypeEchoReply: 364 | if rply, ok := result.Body.(*icmp.Echo); ok { 365 | if t.id != rply.ID { 366 | continue 367 | } 368 | hop.Final = true 369 | return hop 370 | } 371 | case ipv6.ICMPTypeDestinationUnreachable: 372 | if rply, ok := result.Body.(*icmp.Echo); ok { 373 | if t.id != rply.ID { 374 | continue 375 | } 376 | hop.Down = true 377 | return hop 378 | } 379 | case ipv4.ICMPTypeDestinationUnreachable: 380 | if rply, ok := result.Body.(*icmp.Echo); ok { 381 | if t.id != rply.ID { 382 | continue 383 | } 384 | hop.Down = true 385 | return hop 386 | } 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /ptrace.go: -------------------------------------------------------------------------------- 1 | package tracelib 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "golang.org/x/net/icmp" 11 | "golang.org/x/net/ipv4" 12 | "golang.org/x/net/ipv6" 13 | ) 14 | 15 | // expremental implentation of much faster traceroute 16 | // by sending all packets not one after another but at once 17 | 18 | // RunPTrace preforms traceroute to specified host by sending all packets at once 19 | func RunPTrace(host string, source string, source6 string, maxrtt time.Duration, maxttl int, DNScache *LookupCache, rounds int, icmpID int, delay time.Duration) ([][]Hop, error) { 20 | 21 | hops := make([][]Hop, maxttl) 22 | sendOn := make([][]time.Time, maxttl) 23 | for i := 0; i < maxttl; i++ { 24 | hops[i] = make([]Hop, rounds) 25 | for r := 0; r < rounds; r++ { 26 | hops[i][r].Timeout = true 27 | } 28 | sendOn[i] = make([]time.Time, rounds) 29 | } 30 | 31 | var ( 32 | conn net.PacketConn 33 | ipv4conn *ipv4.PacketConn 34 | ipv6conn *ipv6.PacketConn 35 | dest net.Addr 36 | ) 37 | 38 | var err error 39 | isIPv6 := false 40 | 41 | addrList, err := net.LookupIP(host) 42 | if nil != err { 43 | return nil, err 44 | } 45 | 46 | for _, addr := range addrList { 47 | if addr.To4() != nil { 48 | dest, err = net.ResolveIPAddr("ip4:icmp", addr.String()) 49 | break 50 | } 51 | } 52 | 53 | if nil == dest { 54 | for _, addr := range addrList { 55 | if addr.To16() != nil { 56 | isIPv6 = true 57 | dest, err = net.ResolveIPAddr("ip6:58", addr.String()) 58 | break 59 | } 60 | } 61 | } 62 | if nil == dest { 63 | return nil, errors.New("Unable to resolve destination host") 64 | } 65 | 66 | if nil != err { 67 | return nil, err 68 | } 69 | 70 | if !isIPv6 { 71 | conn, err = net.ListenPacket("ip4:icmp", source) 72 | } else { 73 | conn, err = net.ListenPacket("ip6:58", source6) 74 | } 75 | if nil != err { 76 | return nil, err 77 | } 78 | defer conn.Close() 79 | 80 | if isIPv6 { 81 | ipv6conn = ipv6.NewPacketConn(conn) 82 | defer ipv6conn.Close() 83 | if err := ipv6conn.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { 84 | return nil, err 85 | } 86 | var f ipv6.ICMPFilter 87 | f.SetAll(true) 88 | f.Accept(ipv6.ICMPTypeTimeExceeded) 89 | f.Accept(ipv6.ICMPTypeEchoReply) 90 | f.Accept(ipv6.ICMPTypeDestinationUnreachable) 91 | if err := ipv6conn.SetICMPFilter(&f); err != nil { 92 | return nil, err 93 | } 94 | } else { 95 | ipv4conn = ipv4.NewPacketConn(conn) 96 | defer ipv4conn.Close() 97 | } 98 | 99 | go func() { 100 | // sending all packets at once 101 | for i := 1; i <= maxttl; i++ { 102 | hop := i - 1 103 | 104 | var wcm ipv6.ControlMessage 105 | 106 | if nil != ipv4conn { 107 | err := ipv4conn.SetTTL(i) 108 | if nil != err { 109 | for r := 0; r < rounds; r++ { 110 | hops[hop][r].Error = err 111 | } 112 | continue 113 | } 114 | } 115 | if nil != ipv6conn { 116 | wcm.HopLimit = i 117 | } 118 | 119 | for r := 0; r < rounds; r++ { 120 | 121 | var msg icmp.Message 122 | 123 | if isIPv6 { 124 | msg = icmp.Message{Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &icmp.Echo{ID: icmpID, Seq: hop + (maxttl * r)}} 125 | } else { 126 | msg = icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: icmpID, Seq: hop + (maxttl * r)}} 127 | } 128 | netmsg, err := msg.Marshal(nil) 129 | if nil != err { 130 | hops[hop][r].Error = err 131 | continue 132 | } 133 | 134 | sendOn[hop][r] = time.Now() 135 | 136 | if nil != ipv4conn { 137 | _, hops[hop][r].Error = conn.WriteTo(netmsg, dest) 138 | } 139 | if nil != ipv6conn { 140 | _, hops[hop][r].Error = ipv6conn.WriteTo(netmsg, &wcm, dest) 141 | } 142 | 143 | if 0 != delay { 144 | time.Sleep(delay) 145 | } 146 | } 147 | } 148 | }() 149 | 150 | buf := make([]byte, 1500) 151 | maxSeq := rounds*maxttl - 1 152 | 153 | for mtime := time.Now().Add(maxrtt + (delay * time.Duration(maxSeq))); time.Now().Before(mtime); { 154 | conn.SetReadDeadline(mtime) 155 | 156 | readLen, addr, err := conn.ReadFrom(buf) 157 | 158 | if nil != err { 159 | break 160 | } 161 | 162 | var result *icmp.Message 163 | if nil != ipv4conn { 164 | result, err = icmp.ParseMessage(ProtocolICMP, buf[:readLen]) 165 | } 166 | if nil != ipv6conn { 167 | result, err = icmp.ParseMessage(ProtocolICMP6, buf[:readLen]) 168 | } 169 | if nil != err { 170 | // invalid icmp message 171 | continue 172 | } 173 | 174 | switch result.Type { 175 | case ipv4.ICMPTypeEchoReply: 176 | if rply, ok := result.Body.(*icmp.Echo); ok { 177 | 178 | if icmpID != rply.ID { 179 | continue 180 | } 181 | if maxSeq < rply.Seq { 182 | continue 183 | } 184 | 185 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 186 | hops[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since(sendOn[rply.Seq%maxttl][rply.Seq/maxttl]) 187 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Final = true 188 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 189 | } 190 | case ipv4.ICMPTypeTimeExceeded: 191 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 192 | if len(rply.Data) > 26 { 193 | if uint16(icmpID) != binary.BigEndian.Uint16(rply.Data[24:26]) { 194 | continue 195 | } 196 | 197 | seq := int(binary.BigEndian.Uint16(rply.Data[26:28])) 198 | if maxSeq < seq { 199 | continue 200 | } 201 | 202 | hops[seq%maxttl][seq/maxttl].Addr = addr 203 | hops[seq%maxttl][seq/maxttl].RTT = time.Since(sendOn[seq%maxttl][seq/maxttl]) 204 | hops[seq%maxttl][seq/maxttl].Timeout = false 205 | } 206 | } 207 | case ipv6.ICMPTypeTimeExceeded: 208 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 209 | if len(rply.Data) > 46 { 210 | if uint16(icmpID) != binary.BigEndian.Uint16(rply.Data[44:46]) { 211 | continue 212 | } 213 | 214 | seq := int(binary.BigEndian.Uint16(rply.Data[26:28])) 215 | if maxSeq < seq { 216 | continue 217 | } 218 | 219 | hops[seq%maxttl][seq/maxttl].Addr = addr 220 | hops[seq%maxttl][seq/maxttl].RTT = time.Since(sendOn[seq%maxttl][seq/maxttl]) 221 | hops[seq%maxttl][seq/maxttl].Timeout = false 222 | } 223 | } 224 | case ipv6.ICMPTypeEchoReply: 225 | if rply, ok := result.Body.(*icmp.Echo); ok { 226 | if icmpID != rply.ID { 227 | continue 228 | } 229 | if maxSeq < rply.Seq { 230 | continue 231 | } 232 | 233 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 234 | hops[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since(sendOn[rply.Seq%maxttl][rply.Seq/maxttl]) 235 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Final = true 236 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 237 | } 238 | case ipv6.ICMPTypeDestinationUnreachable: 239 | if rply, ok := result.Body.(*icmp.Echo); ok { 240 | if icmpID != rply.ID { 241 | continue 242 | } 243 | if maxSeq < rply.Seq { 244 | continue 245 | } 246 | 247 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 248 | hops[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since(sendOn[rply.Seq%maxttl][rply.Seq/maxttl]) 249 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Down = true 250 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 251 | } 252 | case ipv4.ICMPTypeDestinationUnreachable: 253 | if rply, ok := result.Body.(*icmp.Echo); ok { 254 | if icmpID != rply.ID { 255 | continue 256 | } 257 | if maxSeq < rply.Seq { 258 | continue 259 | } 260 | 261 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 262 | hops[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since(sendOn[rply.Seq%maxttl][rply.Seq/maxttl]) 263 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Down = true 264 | hops[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 265 | } 266 | } 267 | } 268 | 269 | finalHop := maxttl 270 | for hop := 0; hop < maxttl; hop++ { 271 | for r := 0; r < rounds; r++ { 272 | if nil == hops[hop][r].Addr { 273 | continue 274 | } 275 | if nil != DNScache { 276 | addrString := hops[hop][r].Addr.String() 277 | hops[hop][r].Host = DNScache.LookupHost(addrString) 278 | hops[hop][r].AS = DNScache.LookupAS(addrString) 279 | } 280 | if maxttl == finalHop && hops[hop][r].Final { 281 | finalHop = hop + 1 282 | } 283 | 284 | } 285 | } 286 | 287 | return hops[:finalHop], nil 288 | } 289 | 290 | // RunMPTrace preforms traceroute to many hosts by sending all packets at once using one (or 2) raw socket(s) 291 | func RunMPTrace(hosts []string, source string, source6 string, maxrtt time.Duration, maxttl int, DNScache *LookupCache, rounds int, startIcmpID int, delay time.Duration) (map[string]*[][]Hop, error) { 292 | 293 | hops := make(map[string]*[][]Hop, len(hosts)) 294 | sendOn := make(map[string]*[][]time.Time, len(hosts)) 295 | isIPv6 := make(map[string]bool, len(hosts)) 296 | dest := make(map[string]net.Addr, len(hosts)) 297 | addrs := make(map[string]string, len(hosts)) 298 | addrsb := make(map[string][]byte, len(hosts)) 299 | 300 | for _, host := range hosts { 301 | 302 | _hops := make([][]Hop, maxttl) 303 | _sendOn := make([][]time.Time, maxttl) 304 | hops[host] = &_hops 305 | sendOn[host] = &_sendOn 306 | 307 | for i := 0; i < maxttl; i++ { 308 | _hops[i] = make([]Hop, rounds) 309 | for r := 0; r < rounds; r++ { 310 | _hops[i][r].Timeout = true 311 | } 312 | _sendOn[i] = make([]time.Time, rounds) 313 | } 314 | } 315 | 316 | var ( 317 | conn4 net.PacketConn 318 | conn6 net.PacketConn 319 | ipv4conn *ipv4.PacketConn 320 | ipv6conn *ipv6.PacketConn 321 | ) 322 | 323 | var err error 324 | 325 | hasIPv4 := false 326 | hasIPv6 := false 327 | 328 | for _, host := range hosts { 329 | 330 | addrList, err := net.LookupIP(host) 331 | if nil != err { 332 | return nil, err 333 | } 334 | 335 | for _, addr := range addrList { 336 | if addr.To4() != nil { 337 | dest[host], err = net.ResolveIPAddr("ip4:icmp", addr.String()) 338 | addrs[addr.String()] = host 339 | addrsb[host] = []byte(addr.To16()) 340 | hasIPv4 = true 341 | break 342 | } 343 | } 344 | 345 | if nil == dest[host] { 346 | for _, addr := range addrList { 347 | if addr.To16() != nil { 348 | isIPv6[host] = true 349 | dest[host], err = net.ResolveIPAddr("ip6:58", addr.String()) 350 | addrs[addr.String()] = host 351 | addrsb[host] = []byte(addr.To16()) 352 | hasIPv6 = true 353 | break 354 | } 355 | } 356 | } 357 | 358 | if nil == dest[host] { 359 | return nil, errors.New("Unable to resolve destination host for " + host) 360 | } 361 | 362 | if nil != err { 363 | return nil, err 364 | } 365 | 366 | } 367 | 368 | if hasIPv4 { 369 | conn4, err = net.ListenPacket("ip4:icmp", source) 370 | if nil != err { 371 | return nil, err 372 | } 373 | defer conn4.Close() 374 | 375 | ipv4conn = ipv4.NewPacketConn(conn4) 376 | defer ipv4conn.Close() 377 | } 378 | 379 | if hasIPv6 { 380 | conn6, err = net.ListenPacket("ip6:58", source6) 381 | if nil != err { 382 | return nil, err 383 | } 384 | defer conn6.Close() 385 | 386 | ipv6conn = ipv6.NewPacketConn(conn6) 387 | defer ipv6conn.Close() 388 | if err := ipv6conn.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { 389 | return nil, err 390 | } 391 | var f ipv6.ICMPFilter 392 | f.SetAll(true) 393 | f.Accept(ipv6.ICMPTypeTimeExceeded) 394 | f.Accept(ipv6.ICMPTypeEchoReply) 395 | f.Accept(ipv6.ICMPTypeDestinationUnreachable) 396 | if err := ipv6conn.SetICMPFilter(&f); err != nil { 397 | return nil, err 398 | } 399 | } 400 | 401 | go func() { 402 | 403 | // sending all packets at once... grouped not by hosts, but by ttl :) 404 | for i := 1; i <= maxttl; i++ { 405 | 406 | hop := i - 1 407 | 408 | var wcm ipv6.ControlMessage 409 | 410 | if nil != ipv4conn { 411 | err := ipv4conn.SetTTL(i) 412 | if nil != err { 413 | for r := 0; r < rounds; r++ { 414 | for _, host := range hosts { 415 | if !isIPv6[host] { 416 | (*hops[host])[hop][r].Error = err 417 | } 418 | } 419 | } 420 | continue 421 | } 422 | } 423 | if nil != ipv6conn { 424 | wcm.HopLimit = i 425 | } 426 | 427 | for r := 0; r < rounds; r++ { 428 | 429 | for hostid, host := range hosts { 430 | 431 | var icmpType icmp.Type 432 | 433 | if isIPv6[host] { 434 | icmpType = ipv6.ICMPTypeEchoRequest 435 | } else { 436 | icmpType = ipv4.ICMPTypeEcho 437 | } 438 | 439 | msg := icmp.Message{Type: icmpType, Code: 0, Body: &icmp.Echo{ID: startIcmpID + hostid, Seq: hop + (maxttl * r), Data: addrsb[host]}} 440 | netmsg, err := msg.Marshal(nil) 441 | if nil != err { 442 | for _, host := range hosts { 443 | if !isIPv6[host] { 444 | (*hops[host])[hop][r].Error = err 445 | } 446 | } 447 | continue 448 | } 449 | 450 | (*sendOn[host])[hop][r] = time.Now() 451 | 452 | if isIPv6[host] { 453 | _, (*hops[host])[hop][r].Error = ipv6conn.WriteTo(netmsg, &wcm, dest[host]) 454 | } else { 455 | _, (*hops[host])[hop][r].Error = conn4.WriteTo(netmsg, dest[host]) 456 | } 457 | 458 | if 0 != delay { 459 | time.Sleep(delay) 460 | } 461 | 462 | } 463 | 464 | } 465 | } 466 | }() 467 | 468 | maxSeq := rounds*maxttl - 1 469 | 470 | var wg sync.WaitGroup 471 | 472 | // we have up to 2 sockets, so need 2 separate conn.ReadFrom threads 473 | 474 | maxICMPid := startIcmpID + len(hosts) - 1 475 | 476 | if hasIPv4 { 477 | wg.Add(1) 478 | 479 | go func() { 480 | defer wg.Done() 481 | 482 | buf := make([]byte, 1500) 483 | 484 | for mtime := time.Now().Add(maxrtt + (delay * time.Duration(maxSeq))); time.Now().Before(mtime); { 485 | 486 | conn4.SetReadDeadline(mtime) 487 | readLen, addr, err := conn4.ReadFrom(buf) 488 | if nil != err { 489 | break 490 | } 491 | 492 | result, err := icmp.ParseMessage(ProtocolICMP, buf[:readLen]) 493 | if nil != err { 494 | continue // invalid icmp message 495 | } 496 | 497 | switch result.Type { 498 | case ipv4.ICMPTypeEchoReply: 499 | if rply, ok := result.Body.(*icmp.Echo); ok { 500 | 501 | if rply.ID < startIcmpID || rply.ID > maxICMPid { 502 | continue 503 | } 504 | if maxSeq < rply.Seq { 505 | continue 506 | } 507 | 508 | hopS := (*hops[hosts[rply.ID-startIcmpID]]) 509 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 510 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since((*sendOn[hosts[rply.ID-startIcmpID]])[rply.Seq%maxttl][rply.Seq/maxttl]) 511 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Final = true 512 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 513 | } 514 | case ipv4.ICMPTypeTimeExceeded: 515 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 516 | if len(rply.Data) > 26 { 517 | id := int(binary.BigEndian.Uint16(rply.Data[24:26])) 518 | if id < startIcmpID || id > maxICMPid { 519 | continue 520 | } 521 | 522 | seq := int(binary.BigEndian.Uint16(rply.Data[26:28])) 523 | if maxSeq < seq { 524 | continue 525 | } 526 | 527 | hopS := (*hops[hosts[id-startIcmpID]]) 528 | hopS[seq%maxttl][seq/maxttl].Addr = addr 529 | hopS[seq%maxttl][seq/maxttl].RTT = time.Since((*sendOn[hosts[id-startIcmpID]])[seq%maxttl][seq/maxttl]) 530 | hopS[seq%maxttl][seq/maxttl].Timeout = false 531 | } 532 | } 533 | case ipv4.ICMPTypeDestinationUnreachable: 534 | if rply, ok := result.Body.(*icmp.Echo); ok { 535 | if rply.ID < startIcmpID || rply.ID > maxICMPid { 536 | continue 537 | } 538 | if maxSeq < rply.Seq { 539 | continue 540 | } 541 | 542 | hopS := (*hops[hosts[rply.ID-startIcmpID]]) 543 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 544 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since((*sendOn[hosts[rply.ID-startIcmpID]])[rply.Seq%maxttl][rply.Seq/maxttl]) 545 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Down = true 546 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 547 | } 548 | } 549 | } 550 | 551 | }() 552 | } 553 | 554 | if hasIPv6 { 555 | wg.Add(1) 556 | 557 | go func() { 558 | defer wg.Done() 559 | 560 | buf := make([]byte, 1500) 561 | 562 | for mtime := time.Now().Add(maxrtt + (delay * time.Duration(maxSeq))); time.Now().Before(mtime); { 563 | 564 | conn6.SetReadDeadline(mtime) 565 | readLen, addr, err := conn6.ReadFrom(buf) 566 | 567 | if nil != err { 568 | break 569 | } 570 | 571 | result, err := icmp.ParseMessage(ProtocolICMP6, buf[:readLen]) 572 | if nil != err { 573 | continue // invalid icmp message 574 | } 575 | 576 | switch result.Type { 577 | case ipv6.ICMPTypeTimeExceeded: 578 | if rply, ok := result.Body.(*icmp.TimeExceeded); ok { 579 | if len(rply.Data) > 46 { 580 | 581 | id := int(binary.BigEndian.Uint16(rply.Data[44:46])) 582 | seq := int(binary.BigEndian.Uint16(rply.Data[26:28])) 583 | 584 | if id < startIcmpID || id > maxICMPid || maxSeq < seq { 585 | continue 586 | } 587 | 588 | hopS := (*hops[hosts[id-startIcmpID]]) 589 | hopS[seq%maxttl][seq/maxttl].Addr = addr 590 | hopS[seq%maxttl][seq/maxttl].RTT = time.Since((*sendOn[hosts[id-startIcmpID]])[seq%maxttl][seq/maxttl]) 591 | hopS[seq%maxttl][seq/maxttl].Timeout = false 592 | } 593 | } 594 | case ipv6.ICMPTypeEchoReply: 595 | if rply, ok := result.Body.(*icmp.Echo); ok { 596 | 597 | if rply.ID < startIcmpID || rply.ID > maxICMPid || maxSeq < rply.Seq { 598 | continue 599 | } 600 | 601 | hopS := (*hops[hosts[rply.ID-startIcmpID]]) 602 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 603 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since((*sendOn[hosts[rply.ID-startIcmpID]])[rply.Seq%maxttl][rply.Seq/maxttl]) 604 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Final = true 605 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 606 | } 607 | case ipv6.ICMPTypeDestinationUnreachable: 608 | if rply, ok := result.Body.(*icmp.Echo); ok { 609 | 610 | if rply.ID < startIcmpID || rply.ID > maxICMPid || maxSeq < rply.Seq { 611 | continue 612 | } 613 | 614 | hopS := (*hops[hosts[rply.ID-startIcmpID]]) 615 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Addr = addr 616 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].RTT = time.Since((*sendOn[hosts[rply.ID-startIcmpID]])[rply.Seq%maxttl][rply.Seq/maxttl]) 617 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Down = true 618 | hopS[rply.Seq%maxttl][rply.Seq/maxttl].Timeout = false 619 | } 620 | } 621 | } 622 | 623 | }() 624 | } 625 | 626 | wg.Wait() 627 | 628 | for _, host := range hosts { 629 | finalHop := maxttl 630 | hopS := (*hops[host]) 631 | 632 | for hop := 0; hop < maxttl; hop++ { 633 | for r := 0; r < rounds; r++ { 634 | if nil == hopS[hop][r].Addr { 635 | continue 636 | } 637 | if nil != DNScache { 638 | addrString := hopS[hop][r].Addr.String() 639 | hopS[hop][r].Host = DNScache.LookupHost(addrString) 640 | hopS[hop][r].AS = DNScache.LookupAS(addrString) 641 | } 642 | if maxttl == finalHop && hopS[hop][r].Final { 643 | finalHop = hop + 1 644 | } 645 | } 646 | } 647 | hopS = hopS[:finalHop] 648 | hops[host] = &(hopS) 649 | } 650 | 651 | return hops, nil 652 | } 653 | --------------------------------------------------------------------------------