-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5f571b8
Showing
5 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
|
||
} |