From da5880fade3e65ffb9bfe406b5cc5d5fb747fe39 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Sat, 15 May 2021 17:03:12 +0000 Subject: [PATCH] V6 Tracing --- .gitlab-ci.yml | 1 + README.md | 11 +++ go.mod | 1 + go.sum | 3 +- outbound_test.go | 14 +++- session.go | 17 +++-- traceroute.go | 180 +++++++++++++++++++++++++++++++++++++-------- traceroute_test.go | 9 ++- 8 files changed, 197 insertions(+), 39 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b52474..18c1100 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,6 +12,7 @@ stages: - test before_script: + - go get -u golang.org/x/lint/golint unit_tests: stage: test diff --git a/README.md b/README.md index f465812..1234f53 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,17 @@ pkttyagent --process $(ps -xa | grep "netbeans" ) pkttyagent --process $(ps -xa | grep "IntelliJ-IDEA-Ultimate/jbr/bin/java" ) ``` +## Test + +`/etc/sudoers` has restricted PATH environment variable. + +When you run `sudo make test`, `/usr/local/go/bin` must be entered in +`secure_path`. + +``` +Defaults secure_path="....:/usr/local/go/bin" +``` + ## Installation The recommended way to install this package is diff --git a/go.mod b/go.mod index 94ecf48..98a91eb 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/creasty/defaults v1.5.1 github.com/jackpal/gateway v1.0.7 golang.org/x/net v0.0.0-20210510120150-4163338589ed + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect ) diff --git a/go.sum b/go.sum index ea2dc19..3fa52b7 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,9 @@ github.com/jackpal/gateway v1.0.7/go.mod h1:aRcO0UFKt+MgIZmRmvOmnejdDT4Y1DNiNOsS golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/outbound_test.go b/outbound_test.go index 64587a8..4013855 100644 --- a/outbound_test.go +++ b/outbound_test.go @@ -1,6 +1,7 @@ package traceroute import ( + "strings" "testing" ) @@ -8,7 +9,11 @@ func TestGetOutboundIPV6(t *testing.T) { ip, err := getOutboundIP(addressV6) if err != nil { - t.Errorf("this call should not return an error " + err.Error()) + + if !strings.Contains(err.Error(), "no suitable interface was found") { + t.Errorf("this call should not return an error " + err.Error()) + } + return } if !ip.isV6() { @@ -22,7 +27,12 @@ func TestGetOutboundIPV4(t *testing.T) { ip, err := getOutboundIP(addressV4) if err != nil { - t.Errorf("this call should not return an error " + err.Error()) + + if !strings.Contains(err.Error(), "no suitable interface was found") { + t.Errorf("this call should not return an error " + err.Error()) + } + return + } if !ip.isV4() { diff --git a/session.go b/session.go index b4a51f0..8acc7fd 100644 --- a/session.go +++ b/session.go @@ -4,6 +4,7 @@ import ( "github.com/creasty/defaults" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" "time" ) @@ -14,12 +15,15 @@ type Session struct { CallBack func(result Result) - Timeout time.Duration `default:"2"` // in seconds + Timeout time.Duration `default:"5"` // in seconds MaxHops int `default:"30"` + + Mode int nextHop int isFinale bool + ipV6Sock *ipv6.PacketConn ipV4Sock *ipv4.PacketConn icmpEcho icmp.Message readBuffer []byte @@ -40,18 +44,21 @@ func NewSession(destination string) (*Session, error) { } s.Destination = dest - var mode int if dest.isV6() { - mode = addressV6 + s.Mode = addressV6 } else { - mode = addressV4 + s.Mode = addressV4 } s.Timeout = s.Timeout*time.Second //* time.Second - src, err := getOutboundIP(mode) + src, err := getOutboundIP(s.Mode) + if err!=nil { + return nil, err + } + s.Source = src return &s, nil diff --git a/traceroute.go b/traceroute.go index c867c04..7d0fdbd 100644 --- a/traceroute.go +++ b/traceroute.go @@ -5,12 +5,20 @@ import ( "fmt" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" "math/rand" "net" - "time" ) +// from internal/iana/const.go +// Protocol Numbers, Updated: 2017-10-13 +const ( + ProtocolIPv6ICMP = 58 // ICMP for IPv6 + ProtocolICMP = 1 // Internet Control Message +) + +// Result holds the data type Result struct { Hop int Station string @@ -23,7 +31,7 @@ type Results struct { Hops []Result } -func (s *Session) doHop(i int) Result { +func (s *Session) doV4Hop(i int) Result { s.icmpEcho.Body.(*icmp.Echo).Seq = i r := Result{ @@ -62,42 +70,123 @@ func (s *Session) doHop(i int) Result { return r } - readBytes, _, hopNode, err := s.ipV4Sock.ReadFrom(s.readBuffer) + for ; ; { + readBytes, _, hopNode, err := s.ipV4Sock.ReadFrom(s.readBuffer) + + if hopNode != nil { + r.Station = hopNode.String() + } + + if err != nil { + r.Err = err + return r + } + + icmpAnswer, err := icmp.ParseMessage(ProtocolICMP, s.readBuffer[:readBytes]) + + if err != nil { + r.Err = err + return r + } + + latency := time.Since(timeNow) + r.Latency = latency + + if icmpAnswer.Type == ipv4.ICMPTypeTimeExceeded { + s.nextHop++ + return r + } + + if icmpAnswer.Type == ipv4.ICMPTypeEchoReply { + s.isFinale = true + return r + } - if hopNode != nil { - r.Station = hopNode.String() } - if err != nil { - r.Err = err - return r +} + +func (s *Session) doV6Hop(i int) Result { + s.icmpEcho.Body.(*icmp.Echo).Seq = i + + r := Result{ + Hop: i, + Station: "*", } - icmpAnswer, err := icmp.ParseMessage(1, s.readBuffer[:readBytes]) + writeBuffer, err := s.icmpEcho.Marshal(nil) if err != nil { r.Err = err return r } - latency := time.Since(timeNow) - r.Latency = latency + if err := s.ipV6Sock.SetHopLimit(i); err != nil { + r.Err = fmt.Errorf("socket: %w", err) + return r + } - if icmpAnswer.Type == ipv4.ICMPTypeTimeExceeded { - s.nextHop++ + timeNow := time.Now() + + dst := s.Destination + + a := net.IPAddr{ + IP: dst.ip, + Zone: "", + } + + if _, err := s.ipV6Sock.WriteTo(writeBuffer, nil, &a); err != nil { + r.Err = err return r } - if icmpAnswer.Type == ipv4.ICMPTypeEchoReply { - s.isFinale = true + if err := s.ipV6Sock.SetReadDeadline(time.Now().Add(s.Timeout)); err != nil { + r.Err = err return r } - r.Err = fmt.Errorf("unknown icmp answer: %d", icmpAnswer.Type.Protocol()) + for ; ; { + + readBytes, _, hopNode, err := s.ipV6Sock.ReadFrom(s.readBuffer) + + if hopNode != nil { + r.Station = hopNode.String() + } + + if err != nil { + r.Err = err + return r + } + + icmpAnswer, err := icmp.ParseMessage(ProtocolIPv6ICMP, s.readBuffer[:readBytes]) + + if err != nil { + r.Err = err + return r + } + + latency := time.Since(timeNow) + r.Latency = latency + + if icmpAnswer.Type == ipv6.ICMPTypeTimeExceeded { + s.nextHop++ + return r + } + + if icmpAnswer.Type == ipv6.ICMPTypeDestinationUnreachable { + s.nextHop++ + return r + } + + if icmpAnswer.Type == ipv6.ICMPTypeEchoReply { + s.isFinale = true + return r + } + } - return r } +// TraceRouteV4 run V4 Traceroute func (s *Session) TraceRouteV4() (*Results, error) { sock, err := net.ListenPacket("ip4:icmp", s.Source.ip.String()) @@ -107,7 +196,6 @@ func (s *Session) TraceRouteV4() (*Results, error) { } defer sock.Close() - s.ipV4Sock = ipv4.NewPacketConn(sock) defer s.ipV4Sock.Close() @@ -119,14 +207,20 @@ func (s *Session) TraceRouteV4() (*Results, error) { Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ID: rand.Int(), Data: []byte("")}, } + return s.doTrace(func(i int) Result { + return s.doV4Hop(i) + }) +} + +func (s *Session) doTrace(cb func(i int) Result) (*Results, error) { s.readBuffer = make([]byte, 1500) results := Results{} for i := 1; i < s.MaxHops; i++ { - r:=s.doHop(i) + r := cb(i) results.Hops = append(results.Hops, r) - if s.CallBack!=nil { + if s.CallBack != nil { s.CallBack(r) } @@ -138,10 +232,35 @@ func (s *Session) TraceRouteV4() (*Results, error) { return &results, nil } -// currently not implemented -//func (s *Session) traceRouteV6() error { -// return nil -//} +// TraceRouteV6 run V6 trace +func (s *Session) TraceRouteV6() (*Results, error) { + + if s.Source.ip==nil { + return nil, errors.New("missing source ip") + } + + sock, err := net.ListenPacket("ip6:ipv6-icmp", s.Source.ip.String()) + + if err != nil { + return nil, err + } + + defer sock.Close() + s.ipV6Sock = ipv6.NewPacketConn(sock) + defer s.ipV6Sock.Close() + + if err := s.ipV6Sock.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagDst|ipv6.FlagInterface|ipv6.FlagSrc, true); err != nil { + return nil, err + } + + s.icmpEcho = icmp.Message{ + Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &icmp.Echo{ID: rand.Int(), Data: []byte("")}, + } + + return s.doTrace(func(i int) Result { + return s.doV6Hop(i) + }) +} // TraceRoute measures the steps to the target host func (s *Session) TraceRoute() (*Results, error) { @@ -154,12 +273,13 @@ func (s *Session) TraceRoute() (*Results, error) { return results, nil } - // currently not implemented - //if s.Destination.isV6() { - // if err := s.traceRouteV6(); err != nil { - // return nil, err - // } - //} + if s.Destination.isV6() { + results, err := s.TraceRouteV6() + if err != nil { + return nil, err + } + return results, nil + } return nil, errors.New("could not traceroute " + s.Destination.String()) diff --git a/traceroute_test.go b/traceroute_test.go index 13931d4..c07287e 100644 --- a/traceroute_test.go +++ b/traceroute_test.go @@ -2,6 +2,7 @@ package traceroute import ( "fmt" + "strings" "testing" ) @@ -12,6 +13,9 @@ func TestTraceroute(t *testing.T) { shouldSessionError bool }{ { + "ipv6.google.com", + false, + }, { "www.schukai.com", false, }, @@ -23,7 +27,10 @@ func TestTraceroute(t *testing.T) { session, err := NewSession(tc.destination) if err != nil { - t.Errorf("%s shout not error %s", tc.destination, err.Error()) + if !strings.Contains(err.Error(), "no suitable interface was found") { + t.Errorf("%s shout not error %s", tc.destination, err.Error()) + } + return } _,err = session.TraceRoute() -- GitLab