Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
vaegt committed Jun 6, 2017
0 parents commit 5f571b8
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
go-traceroute
=========

## Installation
```bash
go get github.com/pl0th/go-traceroute
cd $GOPATH/src/github.com/pl0th/cmd
go build -o go-traceroute
sudo setcap 'cap_net_raw+p'
```
or

download the latest [release](https://github.com/pl0th/go-traceroute/releases)
124 changes: 124 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
trace "github.com/pl0th/go-traceroute"
"errors"
"net"
"time"
"fmt"
"github.com/urfave/cli"
"os"
c "github.com/fatih/color"
)

func main() {
app := cli.NewApp()
app.Version = "0.1"
app.Name = "go-traceroute"
app.Usage = "A coloured traceroute implemented in golang"
app.Flags = []cli.Flag {
cli.IntFlag{
Name: "ttl, T",
Value: 64,
Usage: "sets the max. TTL value",
},
cli.Float64Flag {
Name: "timeout, o",
Value: 3,
Usage: "sets the timeout for the icmp echo request in seconds",
},
cli.IntFlag{
Name: "tries, t",
Value: 3,
Usage: "sets the amount of tries",
},
cli.BoolFlag{
Name: "colour, c",
Usage: "disables colour",
Destination: &c.NoColor,
},
}


app.Action = func(ctx *cli.Context) (err error) {
if len(ctx.Args()) == 0 {
cli.ShowAppHelp(ctx)
return
}

ip := net.ParseIP(ctx.Args()[0])


if ip == nil {
ips, err := net.LookupIP(ctx.Args()[0])
if err != nil || len(ips) == 0 {
c.Yellow("Please provide a valid IP address or fqdn")
return cli.NewExitError(errors.New(c.RedString("Error: %v", err.Error())), 137)
}
ip = ips[0]
}
traceData := trace.Exec(ip, time.Duration(ctx.Float64("timeout") * float64(time.Second.Nanoseconds())), ctx.Int("tries"), ctx.Int("ttl"))




hops := make([][]printData, 0)
err = traceData.Next()
Loop: for idxTry := 0; err == nil; err = traceData.Next() {
usedIPs := make(map[string][]time.Duration)
hops = append(hops, make([]printData, 0))
for idx := 0; idx < traceData.Tries; idx++ {
hop := traceData.Hops[idx][len(hops)-1]
if len(hop.AddrDNS) == 0 {
traceData.Hops[idx][len(hops)-1].AddrDNS = append(hop.AddrDNS, "no dns entry found")
}

usedIPs[hop.AddrIP.String()] = append(usedIPs[hop.AddrIP.String()], hop.Latency)
hops[len(hops)-1] = append(hops[len(hops)-1], printData{[]time.Duration{hop.Latency}, 1, hop})
}
for idx := 0; idx < traceData.Tries; idx++ {
hop := traceData.Hops[idx][len(hops)-1]
if _, ok := usedIPs[hop.AddrIP.String()]; ok {
addrString := fmt.Sprintf("%v (%v) ", c.YellowString(hop.AddrIP.String()), c.CyanString(hop.AddrDNS[0]))
if hop.AddrIP == nil {
addrString = c.RedString("no response ")
}

fmt.Printf("%v: %v", idxTry, addrString)
for _, lat := range usedIPs[hop.AddrIP.String()] {
latString, formString := lat.String(), ""
if lat > time.Second {
formString = fmt.Sprintf("%v ", latString[:4]+latString[len(latString)-1:])
} else if lat < time.Millisecond && lat > time.Nanosecond {
formString = fmt.Sprintf("%v ", latString[:4]+latString[len(latString)-3:])
} else {
formString = fmt.Sprintf("%v ", latString[:4]+latString[len(latString)-2:])
}
fmt.Printf(c.MagentaString(formString))//µs
}
fmt.Println()
}
delete(usedIPs, hop.AddrIP.String())
if traceData.Dest.Equal(hop.AddrIP) && traceData.Tries == idx + 1 {
break Loop
}
}
idxTry++
}
if err != nil {
c.Yellow("Please make sure you run this command as root")
return cli.NewExitError(errors.New(c.RedString("Error: %v", err.Error())), 137)
}

return
}

app.Run(os.Args)

}

type printData struct {
latencies []time.Duration
count int
trace.Hop
}
25 changes: 25 additions & 0 deletions tracedata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package traceroute

import (
"net"
"time"
)

type traceData struct {
Hops [][]Hop
Dest net.IP
Timeout time.Duration
Tries int
MaxTTL int
}

// Hop represents a path between a source and a destination.
type Hop struct {
TryNumber int
TTL int
AddrIP net.IP
AddrDNS []string //net.IPAddr
Latency time.Duration
Protocol string
Err error
}
122 changes: 122 additions & 0 deletions traceroute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package traceroute

import (
"errors"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"math/rand"
"net"
"time"
)

// Exec returns traceData with initialized Hops.
func Exec(dest net.IP, timeout time.Duration, tries int, maxTTL int) (data traceData) {
return traceData{
Hops: make([][]Hop, tries),
Dest: dest,
Timeout: timeout,
Tries: tries,
MaxTTL: maxTTL,
}

}

// Next executes the doHop method for every try.
func (data *traceData) Next() (err error) {
ttl := len(data.Hops[0]) + 1
if ttl > data.MaxTTL {
return errors.New("Maximum TTL reached")
}
for try := 0; try < data.Tries; try++ {
currentHop, err := doHop(ttl, data.Dest, data.Timeout)
if err != nil {
return err
}
if currentHop.Err == nil {
currentHop.AddrDNS, err = net.LookupAddr(currentHop.AddrIP.String()) // maybe use memoization
}
currentHop.TryNumber = try
data.Hops[try] = append(data.Hops[try], currentHop)
}
return
}

func doHop(ttl int, dest net.IP, timeout time.Duration) (currentHop Hop, err error) {
conn, err := net.Dial("ip4:icmp", dest.String())
if err != nil {
return
}
defer conn.Close()
newConn := ipv4.NewConn(conn)
if err = newConn.SetTTL(ttl); err != nil {
return
}
echo := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: rand.Int(),
Seq: 1, // TODO Sequence should be incremented every Hop & the id should be changed on every try(not random but different)
Data: []byte("TABS"),
}}

req, err := echo.Marshal(nil)
if err != nil {
return
}
packetConn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return
}
defer packetConn.Close()

start := time.Now()
_, err = conn.Write(req)

if err != nil {
return
}
if err = packetConn.SetDeadline(time.Now().Add(timeout)); err != nil {
return
}

readBytes := make([]byte, 1500) // 1500 Bytes ethernet MTU
_, sAddr, connErr := packetConn.ReadFrom(readBytes) // first return value (Code) might be useful

latency := time.Since(start)

currentHop = Hop{
TTL: ttl,
Protocol: "icmp",
Latency: latency,
Err: connErr,
}

if connErr == nil {
currentHop.AddrIP = net.ParseIP(sAddr.String())
if currentHop.AddrIP == nil {
currentHop.Err = errors.New("timeout reached")
}
}

return currentHop, err
}

func (data *traceData) All() (err error) {
for try := 0; try < data.Tries; try++ {
for ttl := 1; ttl <= data.MaxTTL; ttl++ {
currentHop, err := doHop(ttl, data.Dest, data.Timeout)
if err != nil {
return err
}
if currentHop.Err == nil {
currentHop.AddrDNS, err = net.LookupAddr(currentHop.AddrIP.String()) // maybe use memoization
}
currentHop.TryNumber = try
data.Hops[try] = append(data.Hops[try], currentHop)
if currentHop.Err == nil && data.Dest.Equal(currentHop.AddrIP) {
break
}
}
}
return
}
55 changes: 55 additions & 0 deletions traceroute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package traceroute

import (
"net"
"reflect"
"testing"
"time"
)

func TestExec(test *testing.T) {
expected := returnData()

got := Exec(expected.Dest, expected.Timeout, expected.Tries, expected.MaxTTL)
if !reflect.DeepEqual(expected, got) {
test.Errorf("Error: Exec data expected %v got %v", expected, got)
}
}

func TestNext(test *testing.T) {
data := returnData()

err := data.Next()

if err != nil {
if err.Error() == "dial ip4:icmp 8.8.8.8: socket: operation not permitted" {
test.Errorf("Error: Please run this test as root\n%v", err.Error())
} else {
test.Errorf("Error: %v", err.Error())
}
}
}

func TestAll(test *testing.T) {
data := returnData()

err := data.All()

if err != nil {
if err.Error() == "dial ip4:icmp 8.8.8.8: socket: operation not permitted" {
test.Errorf("Error: Please run this test as root\n%v", err.Error())
} else {
test.Errorf("Error: %v", err.Error())
}
}
}

func returnData() traceData {
dest := net.ParseIP("8.8.8.8")
timeout := 3 * time.Second
tries := 4
maxTTL := 32

return traceData{make([][]Hop, tries), dest, timeout, tries, maxTTL}

}

0 comments on commit 5f571b8

Please sign in to comment.