Skip to content

Commit

Permalink
#109: Graphviz support (#115)
Browse files Browse the repository at this point in the history
* #109: Graphviz: initial code

* #109: Lightcolored theme

* #109: Add usage of constants

* #109: Add missing semicolon

* Refactor DotTemplateData

* Exclude last hop from the tracing

* Add space for graphviz property

* Add more unit tests
  • Loading branch information
vdjagilev authored Jul 23, 2022
1 parent 27797e1 commit 9c7b901
Show file tree
Hide file tree
Showing 11 changed files with 563 additions and 7 deletions.
6 changes: 5 additions & 1 deletion formatter/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ const (
MarkdownOutput OutputFormat = "md"
// JSONOutput constant defines OutputFormat for JavaScript Object Notation, which is more useful for machine-related operations (parsing)
JSONOutput OutputFormat = "json"
// DotOutput constant defined OutputFormat for Dot (Graphviz), which can be used to generate various graphs
DotOutput OutputFormat = "dot"
)

// IsValid checks whether requested output format is valid
func (of OutputFormat) IsValid() bool {
// markdown & md is essentially the same thing
switch of {
case "markdown", "md", "html", "csv", "json":
case "markdown", "md", "html", "csv", "json", "dot":
return true
}
return false
Expand All @@ -35,6 +37,8 @@ func (of OutputFormat) FileOutputFormat() OutputFormat {
return CSVOutput
case "json":
return JSONOutput
case "dot":
return DotOutput
}
return HTMLOutput
}
10 changes: 10 additions & 0 deletions formatter/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func TestOutputFormat_FileOutputFormat(t *testing.T) {
of: "json",
want: "json",
},
{
name: "dot",
of: "dot",
want: "dot",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -87,6 +92,11 @@ func TestOutputFormat_IsValid(t *testing.T) {
of: "csv",
want: true,
},
{
name: "dot",
of: "dot",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func New(config *Config) Formatter {
return &CSVFormatter{
config,
}
case DotOutput:
return &DotFormatter{
config,
}
}
return nil
}
Expand Down
111 changes: 111 additions & 0 deletions formatter/formatter_dot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package formatter

import (
_ "embed"
"fmt"
"strings"
"text/template"
)

type DotFormatter struct {
config *Config
}

//go:embed resources/templates/graphviz.tmpl
// DotTemplate variable is used to store contents of graphviz template
var DotTemplate string

const (
DotOpenPortColor = "#228B22"
DotFilteredPortColor = "#FFAE00"
DotClosedPortColor = "#DC143C"
DotDefaultColor = "gray"

DotFontStyle = "monospace"

DotLayout = "dot"
)

var DotDefaultOptions = map[string]string{
"default_font": DotFontStyle,
"layout": DotLayout,
"color_default": DotDefaultColor,
}

type DotTemplateData struct {
NMAPRun *NMAPRun
Constants map[string]string
}

// Format the data and output it to appropriate io.Writer
func (f *DotFormatter) Format(td *TemplateData, templateContent string) (err error) {
tmpl := template.New("dot")
f.defineTemplateFunctions(tmpl)
tmpl, err = tmpl.Parse(templateContent)
if err != nil {
return
}
dotTemplateData := DotTemplateData{
NMAPRun: &td.NMAPRun,
Constants: DotDefaultOptions,
}
return tmpl.Execute(f.config.Writer, dotTemplateData)
}

// defaultTemplateContent returns default template content for any typical chosen formatter (HTML or Markdown)
func (f *DotFormatter) defaultTemplateContent() string {
return DotTemplate
}

// defineTemplateFunctions defines all template functions that are used in dot templates
func (f *DotFormatter) defineTemplateFunctions(tmpl *template.Template) {
tmpl.Funcs(
template.FuncMap{
"clean_ip": cleanIP,
"port_state_color": portStateColor,
"hop_list": hopList,
},
)
}

// cleanIP removes dots from IP address to make it possible to use in graphviz as an ID
func cleanIP(ip string) string {
return strings.ReplaceAll(ip, ".", "")
}

// portStateColor returns hexademical color value for state port
func portStateColor(port *Port) string {
switch port.State.State {
case "open":
return DotOpenPortColor
case "filtered":
return DotFilteredPortColor
case "closed":
return DotClosedPortColor
}
return DotDefaultColor
}

// hopList function returns a map with a list of hops where very first hop is `startHop` (scanner itself)
func hopList(hops []Hop, startHop string, endHopName string, endHopKey int) map[string]string {
var hopList map[string]string = map[string]string{}
var previous *Hop = nil
for i := range hops {
// Skip last hop, because it has the same IP as the target server
if i == len(hops)-1 {
break
}
if i == 0 {
hopList[startHop] = fmt.Sprintf("hop%s", hops[i].IPAddr)
} else {
hopList[fmt.Sprintf("hop%s", previous.IPAddr)] = fmt.Sprintf("hop%s", hops[i].IPAddr)
}
previous = &hops[i]
}
if previous != nil {
hopList[fmt.Sprintf("hop%s", previous.IPAddr)] = fmt.Sprintf("%s%d", endHopName, endHopKey)
} else {
hopList[startHop] = fmt.Sprintf("%s%d", endHopName, endHopKey)
}
return hopList
}
Loading

0 comments on commit 9c7b901

Please # to comment.