Skip to content

Commit

Permalink
[Go] Added Results.ToDOT, and cmd/todot utility
Browse files Browse the repository at this point in the history
Signed-off-by: Andrea Barberio <insomniac@slackware.it>
  • Loading branch information
insomniacslk committed Mar 10, 2023
1 parent 859ee5b commit 7554a8d
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.20

require (
github.com/florianl/go-nfqueue v1.3.1
github.com/goccy/go-graphviz v0.1.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.2
Expand All @@ -13,12 +14,15 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
19 changes: 19 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA=
github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJR6Gw=
github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/goccy/go-graphviz v0.1.0 h1:6OqQoQ5PeAiHYe/YcusyeulqBrOkUb16HQ4ctRdyVUU=
github.com/goccy/go-graphviz v0.1.0/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -28,12 +39,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -45,6 +63,7 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
61 changes: 61 additions & 0 deletions go/dublintraceroute/cmd/todot/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: BSD-2-Clause */

package main

import (
"encoding/json"
"fmt"
"go/build"
"log"
"os"

flag "github.com/spf13/pflag"

"github.com/insomniacslk/dublin-traceroute/go/dublintraceroute/results"
)

func init() {
// Ensure that CGO is disabled
var ctx build.Context
if ctx.CgoEnabled {
fmt.Println("Disabling CGo")
ctx.CgoEnabled = false
}
}

var (
flagOutputFile = flag.StringP("output", "o", "-", "Output file. Use \"-\" to print to standard output")
)

func main() {

flag.Parse()

if len(flag.Args()) != 1 {
log.Fatal("Missing JSON file name")
}

buf, err := os.ReadFile(flag.Arg(0))
if err != nil {
log.Fatalf("Failed to read file '%s': %v", flag.Arg(0), err)
}

var result results.Results
if err := json.Unmarshal(buf, &result); err != nil {
log.Fatalf("Failed to unmarshal JSON into Results: %v", err)
}
output, err := result.ToDOT()
if err != nil {
log.Fatalf("Failed to convert to DOT: %v", err)
}
if *flagOutputFile == "-" {
fmt.Println(output)
} else {
err := os.WriteFile(*flagOutputFile, []byte(output), 0644)
if err != nil {
log.Fatalf("Failed to write DOT file: %v", err)
}
log.Printf("Saved DOT file to %s", *flagOutputFile)
log.Printf("Run `dot -Tpng \"%s\" -o \"%s.png\"` to convert to PNG", *flagOutputFile, *flagOutputFile)
}
}
124 changes: 124 additions & 0 deletions go/dublintraceroute/results/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
package results

import (
"bytes"
"encoding/json"
"fmt"
"log"
"math/rand"
"net"
"sort"
"strconv"
"strings"
"time"

"github.com/goccy/go-graphviz"
"github.com/goccy/go-graphviz/cgraph"
)

// IP represents some information from the IP header.
Expand Down Expand Up @@ -160,3 +167,120 @@ func (r *Results) ToJSON(compress bool, indent string) string {
}
return string(b)
}

// ToDOT encodes a Results object to a DOT file suitable for GraphViz
func (r *Results) ToDOT() (string, error) {
type node struct {
node *cgraph.Node
probe *Probe
}
gv := graphviz.New()
graph, err := gv.Graph()
if err != nil {
return "", fmt.Errorf("failed to create graph: %w", err)
}
graph.SetRankDir(cgraph.BTRank)

flowIDs := make([]int, 0, len(r.Flows))
for flowID := range r.Flows {
flowIDs = append(flowIDs, int(flowID))
}
sort.Ints(flowIDs)

for _, flowID := range flowIDs {
hops := r.Flows[uint16(flowID)]
if len(hops) == 0 {
log.Printf("No hops for flow ID %d", flowID)
continue
}
var nodes []node
// add first hop
firstNodeName := hops[0].Sent.IP.SrcIP.String()
firstHop, err := graph.CreateNode(firstNodeName)
if err != nil {
return "", fmt.Errorf("failed to create first node: %w", err)
}
firstHop.SetShape(cgraph.RectShape)
nodes = append(nodes, node{node: firstHop, probe: &hops[0]})

// then add all the other hops
for idx, hop := range hops {
hop := hop
nodename := fmt.Sprintf("NULL - %d", idx)
label := "*"
hostname := ""
if hop.Received != nil {
nodename = hop.Received.IP.SrcIP.String()
if hop.Name != nodename {
hostname = "\n" + hop.Name
}
// MPLS labels
mpls := ""
if len(hop.Received.ICMP.MPLSLabels) > 0 {
mpls = "MPLS labels: \n"
for _, mplsLabel := range hop.Received.ICMP.MPLSLabels {
mpls += fmt.Sprintf(" - %d, ttl: %d\n", mplsLabel.Label, mplsLabel.TTL)
}
}
label = fmt.Sprintf("%s%s\n%s\n%s", nodename, hostname, hop.Received.ICMP.Description, mpls)
}
n, err := graph.CreateNode(nodename)
if err != nil {
return "", fmt.Errorf("failed to create node '%s': %w", nodename, err)
}
if hop.IsLast {
n.SetShape(cgraph.RectShape)
}
n.SetLabel(label)
nodes = append(nodes, node{node: n, probe: &hop})

if hop.IsLast {
break
}
}
// add edges
if len(nodes) <= 1 {
// no edges to add if there is only one node
continue
}
color := rand.Intn(0xffffff)
// start at node 1. Each node back-references the previous one
for idx := 1; idx < len(nodes); idx++ {
if idx >= len(nodes) {
// we are at the second-to-last node
break
}
prev := nodes[idx-1]
cur := nodes[idx]
edgeName := fmt.Sprintf("%s - %s - %d - %d", prev.node.Name(), cur.node.Name(), idx, flowID)
edgeLabel := ""
if idx == 1 {
edgeLabel += fmt.Sprintf(
"srcport %d\ndstport %d",
cur.probe.Sent.UDP.SrcPort,
cur.probe.Sent.UDP.DstPort,
)
}
if prev.probe.NATID != cur.probe.NATID {
edgeLabel += "\nNAT detected"
}
edgeLabel += fmt.Sprintf("\n%d.%d ms", int(cur.probe.RttUsec/1000), int(cur.probe.RttUsec%1000))

edge, err := graph.CreateEdge(edgeName, prev.node, cur.node)
if err != nil {
return "", fmt.Errorf("failed to create edge '%s': %w", edgeName, err)
}
edge.SetLabel(edgeLabel)
edge.SetColor(fmt.Sprintf("#%06x", color))
}
}
var buf bytes.Buffer
if err := gv.Render(graph, "dot", &buf); err != nil {
return "", fmt.Errorf("failed to render graph: %w", err)
}
if err := graph.Close(); err != nil {
return "", fmt.Errorf("failed to close graph: %w", err)
}
gv.Close()
return buf.String(), nil
}

0 comments on commit 7554a8d

Please sign in to comment.