Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

darwin non-root traceroute #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A traceroute library written in Go.

Must be run as sudo on OS X (and others)?
~~Must be run as sudo on OS X (and others)?~~

## CLI App

Expand Down
109 changes: 51 additions & 58 deletions traceroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import (
"net"
"syscall"
"time"

"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)

const DEFAULT_PORT = 33434
const DEFAULT_MAX_HOPS = 64
const DEFAULT_FIRST_HOP = 1
const DEFAULT_TIMEOUT_MS = 500
const DEFAULT_RETRIES = 3
const DEFAULT_PACKET_SIZE = 52
const DEFAULT_PACKET_SIZE = 1500

// Return the first non-loopback address as a 4 byte IP address. This address
// is used for sending packets out.
Expand Down Expand Up @@ -131,11 +134,10 @@ func (options *TracerouteOptions) SetPacketSize(packetSize int) {

// TracerouteHop type
type TracerouteHop struct {
Success bool
Address [4]byte
Host string
N int
ElapsedTime time.Duration
ElapsedTime []time.Duration
TTL int
}

Expand Down Expand Up @@ -179,85 +181,76 @@ func closeNotify(channels []chan TracerouteHop) {
func Traceroute(dest string, options *TracerouteOptions, c ...chan TracerouteHop) (result TracerouteResult, err error) {
result.Hops = []TracerouteHop{}
destAddr, err := destAddr(dest)
result.DestinationAddress = destAddr
socketAddr, err := socketAddr()
if err != nil {
return
}

result.DestinationAddress = destAddr
// socketAddr, err := socketAddr()
// if err != nil {
// return
// }

timeoutMs := (int64)(options.TimeoutMs())
tv := syscall.NsecToTimeval(1000 * 1000 * timeoutMs)

ttl := options.FirstHop()
retry := 0
for {
//log.Println("TTL: ", ttl)
start := time.Now()
// Set up the socket to send packets out.
sendSocket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_ICMP)
if err != nil {
return result, err
}
defer syscall.Close(sendSocket)

// Set up the socket to receive inbound packets
recvSocket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)
if err != nil {
return result, err
}
// This sets the timeout to wait for a response from the remote host
syscall.SetsockoptTimeval(sendSocket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv)

// Set up the socket to send packets out.
sendSocket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
finished := false
for ttl := options.FirstHop(); ttl < options.MaxHops() && !finished; ttl++ {
// This sets the current hop TTL
syscall.SetsockoptInt(sendSocket, syscall.IPPROTO_IP, syscall.IP_TTL, ttl)

wm := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: ttl & 0xffff, Data: []byte("R-U-OK?")},
}
wm.Body.(*icmp.Echo).Seq = ttl
wb, err := wm.Marshal(nil)
if err != nil {
return result, err
}
// This sets the current hop TTL
syscall.SetsockoptInt(sendSocket, 0x0, syscall.IP_TTL, ttl)
// This sets the timeout to wait for a response from the remote host
syscall.SetsockoptTimeval(recvSocket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv)

defer syscall.Close(recvSocket)
defer syscall.Close(sendSocket)
var hop TracerouteHop
hop.ElapsedTime = make([]time.Duration, options.Retries())

// Bind to the local socket to listen for ICMP packets
syscall.Bind(recvSocket, &syscall.SockaddrInet4{Port: options.Port(), Addr: socketAddr})
for i := 0; i < options.Retries(); i++ {
start := time.Now()

// Send a single null byte UDP packet
syscall.Sendto(sendSocket, []byte{0x0}, 0, &syscall.SockaddrInet4{Port: options.Port(), Addr: destAddr})
syscall.Sendto(sendSocket, wb, 0, &syscall.SockaddrInet4{Port: options.Port(), Addr: destAddr})

var p = make([]byte, options.PacketSize())
n, from, err := syscall.Recvfrom(recvSocket, p, 0)
elapsed := time.Since(start)
if err == nil {
currAddr := from.(*syscall.SockaddrInet4).Addr
var p = make([]byte, options.PacketSize())
_, from, err := syscall.Recvfrom(sendSocket, p, 0)
elapsed := time.Since(start)

hop := TracerouteHop{Success: true, Address: currAddr, N: n, ElapsedTime: elapsed, TTL: ttl}
hop.TTL = ttl
hop.N = i

// TODO: this reverse lookup appears to have some standard timeout that is relatively
// high. Consider switching to something where there is greater control.
currHost, err := net.LookupAddr(hop.AddressString())
if err == nil {
hop.Host = currHost[0]
}
currAddr := from.(*syscall.SockaddrInet4).Addr

notify(hop, c)
hop.ElapsedTime[i] = elapsed
hop.Address = currAddr

result.Hops = append(result.Hops, hop)

ttl += 1
retry = 0

if ttl > options.MaxHops() || currAddr == destAddr {
closeNotify(c)
return result, nil
}
} else {
retry += 1
if retry > options.Retries() {
notify(TracerouteHop{Success: false, TTL: ttl}, c)
ttl += 1
retry = 0
}
notify(hop, c)

if ttl > options.MaxHops() {
closeNotify(c)
return result, nil
if currAddr == destAddr {
finished = true
}
} else {
notify(hop, c)
}
}
result.Hops = append(result.Hops, hop)

}
closeNotify(c)
return result, nil
}
8 changes: 4 additions & 4 deletions traceroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
)

func printHop(hop TracerouteHop) {
fmt.Printf("%-3d %v (%v) %v\n", hop.TTL, hop.HostOrAddressString(), hop.AddressString(), hop.ElapsedTime)
fmt.Printf("%-3d %v %v\n", hop.TTL, hop.HostOrAddressString(), hop.ElapsedTime)
}

func TestTraceroute(t *testing.T) {
fmt.Println("Testing synchronous traceroute\n")
out, err := Traceroute("google.com", new(TracerouteOptions))
fmt.Println("Testing synchronous traceroute")
out, err := Traceroute("www.qq.com", new(TracerouteOptions))
if err == nil {
if len(out.Hops) == 0 {
t.Errorf("TestTraceroute failed. Expected at least one hop")
Expand All @@ -27,7 +27,7 @@ func TestTraceroute(t *testing.T) {
}

func TestTraceouteChannel(t *testing.T) {
fmt.Println("Testing asynchronous traceroute\n")
fmt.Println("Testing asynchronous traceroute")
c := make(chan TracerouteHop, 0)
go func() {
for {
Expand Down