Skip to content
Merged
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
87 changes: 13 additions & 74 deletions cmd/flow_capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,6 @@ var (
flowsToShow = 35
regexes = []string{}
lastFlows = []config.GenericMap{}

rawDisplay = "Raw"
standardDisplay = "Standard"
exclusiveDisplays = []string{rawDisplay, standardDisplay}
pktDropDisplay = "pktDrop"
dnsDisplay = "dnsTracking"
rttDisplay = "flowRTT"
networkEventsDisplay = "networkEvents"
displays = []string{pktDropDisplay, dnsDisplay, rttDisplay, networkEventsDisplay}
display = []string{standardDisplay}

noEnrichment = "None"
exclusiveEnrichments = []string{noEnrichment}
clusterEnrichment = "Cluster"
zoneEnrichment = "Zone"
hostEnrichment = "Host"
ownerEnrichment = "Owner"
resourceEnrichment = "Resource"
subnetLabelEnrichment = "SubnetLabel"
enrichments = []string{clusterEnrichment, zoneEnrichment, hostEnrichment, ownerEnrichment, resourceEnrichment, subnetLabelEnrichment}
enrichment = []string{resourceEnrichment}
)

func runFlowCapture(_ *cobra.Command, _ []string) {
Expand Down Expand Up @@ -256,16 +235,16 @@ func updateTable() {
}
if strings.Contains(options, "background=true") {
fmt.Printf("Showing last: %d\n", flowsToShow)
fmt.Printf("Display: %s\n", toShortTitleStr(display))
fmt.Printf("Enrichment: %s\n", toShortTitleStr(enrichment))
fmt.Printf("Display: %s\n", display.getCurrentItem().name)
fmt.Printf("Enrichment: %s\n", enrichment.getCurrentItem().name)
} else {
fmt.Printf("Showing last: %d Use Up / Down keyboard arrows to increase / decrease limit\n", flowsToShow)
fmt.Printf("Display: %s Use Left / Right keyboard arrows to cycle views\n", toShortTitleStr(display))
fmt.Printf("Enrichment: %s Use Page Up / Page Down keyboard keys to cycle enrichment scopes\n", toShortTitleStr(enrichment))
fmt.Printf("Display: %s Use Left / Right keyboard arrows to cycle views\n", display.getCurrentItem().name)
fmt.Printf("Enrichment: %s Use Page Up / Page Down keyboard keys to cycle enrichment scopes\n", enrichment.getCurrentItem().name)
}
}

if slices.Contains(display, rawDisplay) {
if display.getCurrentItem().name == rawDisplay {
fmt.Print("Raw flow logs:\n")
for _, flow := range lastFlows {
fmt.Printf("%v\n", flow)
Expand All @@ -282,23 +261,8 @@ func updateTable() {
}

// enrichment fields
if !slices.Contains(enrichment, noEnrichment) {
for _, enr := range enrichment {
var fieldMatch string
if enr == resourceEnrichment {
fieldMatch = "K8S_Name"
} else if enr == subnetLabelEnrichment {
fieldMatch = "SubnetLabel"
} else {
fieldMatch = fmt.Sprintf("K8S_%s", enr)
}

for _, col := range cfg.Columns {
if strings.Contains(col.Field, fieldMatch) {
colIDs = append(colIDs, col.ID)
}
}
}
if enrichment.getCurrentItem().name != noOptions {
colIDs = append(colIDs, enrichment.getCurrentItem().ids...)
} else {
// TODO: add a new flag in the config to identify these as default non enriched fields
colIDs = append(colIDs,
Expand All @@ -310,16 +274,15 @@ func updateTable() {
}

// standard / feature fields
if !slices.Contains(display, standardDisplay) {
if display.getCurrentItem().name != standardDisplay {
for _, col := range cfg.Columns {
if slices.Contains(display, col.Feature) {
if slices.Contains(display.getCurrentItem().ids, col.Feature) {
colIDs = append(colIDs, col.ID)
}
}
} else {
// TODO: add a new flag in the config to identify these as default feature fields
colIDs = append(colIDs,
"FlowDirection",
"Interfaces",
"Proto",
"Dscp",
Expand Down Expand Up @@ -366,30 +329,6 @@ func updateTable() {
}
}

func cycleOption(selection []string, exclusiveOptions []string, options []string, incr int) []string {
allOptions := slices.Concat(exclusiveOptions, options)

var index int
if len(selection) == 1 {
index = slices.Index(allOptions, selection[0])
if index+incr < 0 || index+incr > len(allOptions)-1 {
index = -1
} else {
index += incr
}
} else if incr < 0 {
index = len(allOptions) - 1
}

if index != -1 {
selection = []string{allOptions[index]}
} else {
selection = slices.Clone(options)
}

return selection
}

// scanner returns true in case of normal exit (end of program execution) or false in case of error
func scanner() bool {
if err := keyboard.Open(); err != nil {
Expand Down Expand Up @@ -417,13 +356,13 @@ func scanner() bool {
flowsToShow--
}
case key == keyboard.KeyArrowRight:
display = cycleOption(display, exclusiveDisplays, displays, 1)
display.next()
case key == keyboard.KeyArrowLeft:
display = cycleOption(display, exclusiveDisplays, displays, -1)
display.prev()
case key == keyboard.KeyPgup:
enrichment = cycleOption(enrichment, exclusiveEnrichments, enrichments, 1)
enrichment.next()
case key == keyboard.KeyPgdn:
enrichment = cycleOption(enrichment, exclusiveEnrichments, enrichments, -1)
enrichment.prev()
case key == keyboard.KeyBackspace || key == keyboard.KeyBackspace2:
if len(regexes) > 0 {
lastIndex := len(regexes) - 1
Expand Down
61 changes: 36 additions & 25 deletions cmd/flow_capture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func TestFlowTableDefaultDisplay(t *testing.T) {
rows := strings.Split(buf.String(), "\n")

assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src Name Src Namespace Dst Name Dst Namespace Node Dir Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
assert.Equal(t, `17:25:28.703000 src-pod first-namespace dst-pod second-namespace Ingress f18b970c2ce8fdd TCP Standard 456B 5 `, rows[1])
assert.Equal(t, `--------------- --------------- --------------- --------------- --------------- ---------- ---------- ---------- ---------- ----- ----- `, rows[2])
assert.Equal(t, `End Time Src Kind Dst Kind Src Name Dst Name Src Namespace Dst Namespace Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
assert.Equal(t, `17:25:28.703000 Pod Pod src-pod dst-pod first-namespace second-namespace f18b970c2ce8fdd TCP Standard 456B 5 `, rows[1])
assert.Equal(t, `--------------- ---------- ---------- --------------- --------------- --------------- --------------- ---------- ---------- ---------- ----- ----- `, rows[2])
assert.Empty(t, rows[3])
}

Expand All @@ -53,8 +53,8 @@ func TestFlowTableMultipleFlows(t *testing.T) {
setOutputBuffer(&buf)

// set display to standard without enrichment
display = []string{standardDisplay}
enrichment = []string{noEnrichment}
display.current = 1
enrichment.current = 0

// set time and bytes per flow
flowTime := 1704063600000
Expand Down Expand Up @@ -86,16 +86,16 @@ func TestFlowTableMultipleFlows(t *testing.T) {
rows := strings.Split(buf.String(), "\n")
// table must display only 38 rows (35 flows + header + footer + empty line)
assert.Equal(t, 38, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Node Dir Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
// first flow is the 6th one that came to the display
assert.Equal(t, `00:00:06.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 6KB 1 `, rows[1])
assert.Equal(t, `00:00:07.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 7KB 1 `, rows[2])
assert.Equal(t, `00:00:08.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 8KB 1 `, rows[3])
assert.Equal(t, `00:00:09.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 9KB 1 `, rows[4])
assert.Equal(t, `00:00:10.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 10KB 1 `, rows[5])
assert.Equal(t, `00:00:06.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 6KB 1 `, rows[1])
assert.Equal(t, `00:00:07.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 7KB 1 `, rows[2])
assert.Equal(t, `00:00:08.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 8KB 1 `, rows[3])
assert.Equal(t, `00:00:09.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 9KB 1 `, rows[4])
assert.Equal(t, `00:00:10.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 10KB 1 `, rows[5])
// last flow is the 40th one
assert.Equal(t, `00:00:40.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a n/a 40KB 1 `, rows[35])
assert.Equal(t, `--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----- ----- `, rows[36])
assert.Equal(t, `00:00:40.000000 10.0.0.5 n/a 10.0.0.6 n/a n/a n/a n/a 40KB 1 `, rows[35])
assert.Equal(t, `--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----- ----- `, rows[36])
assert.Empty(t, rows[37])

}
Expand All @@ -108,10 +108,21 @@ func TestFlowTableAdvancedDisplay(t *testing.T) {
setOutputBuffer(&buf)

// getRows function cleanup everything and redraw table with sample flow
getRows := func(d []string, e []string) []string {
getRows := func(displayName string, displayIds []string, enrichmentName string, enrichmentIds []string) []string {
// prepare display options
display = d
enrichment = e
display = option{
all: []optionItem{
{name: displayName, ids: displayIds},
},
current: 0,
}

enrichment = option{
all: []optionItem{
{name: enrichmentName, ids: enrichmentIds},
},
current: 0,
}

// clear filters and previous flows
regexes = []string{}
Expand All @@ -127,24 +138,24 @@ func TestFlowTableAdvancedDisplay(t *testing.T) {
}

// set display without enrichment
rows := getRows([]string{pktDropDisplay, dnsDisplay, rttDisplay, networkEventsDisplay}, []string{noEnrichment})
rows := getRows(allOptions, []string{pktDropFeature, dnsFeature, rttFeature, networkEventsDisplay}, noOptions, []string{})
assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Dropped Bytes Dropped Packets Drop State Drop Cause Drop Flags DNS Id DNS Latency DNS RCode DNS Error Flow RTT Network Events `, rows[0])
assert.Equal(t, `17:25:28.703000 10.128.0.29 1234 10.129.0.26 5678 32B 1 TCP_INVALID_STATE SKB_DROP_REASON_TCP_INVALID_SEQUENCE 16 31319 1ms NoError 0 10µs hello `, rows[1])
assert.Equal(t, `--------------- ---------- ---------- ---------- ---------- ----- ----- ---------- ---------- ---------- ----- ----- ----- ----- ----- --------------- `, rows[2])
assert.Empty(t, rows[3])

// set display to standard
rows = getRows([]string{standardDisplay}, []string{noEnrichment})
rows = getRows(standardDisplay, []string{}, noOptions, []string{})

assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Node Dir Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
assert.Equal(t, `17:25:28.703000 10.128.0.29 1234 10.129.0.26 5678 Ingress f18b970c2ce8fdd TCP Standard 456B 5 `, rows[1])
assert.Equal(t, `--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----- ----- `, rows[2])
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Interfaces L3 Layer Protocol L3 Layer DSCP Bytes Packets `, rows[0])
assert.Equal(t, `17:25:28.703000 10.128.0.29 1234 10.129.0.26 5678 f18b970c2ce8fdd TCP Standard 456B 5 `, rows[1])
assert.Equal(t, `--------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----- ----- `, rows[2])
assert.Empty(t, rows[3])

// set display to pktDrop
rows = getRows([]string{pktDropDisplay}, []string{noEnrichment})
rows = getRows("Packet drops", []string{pktDropFeature}, noOptions, []string{})

assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Dropped Bytes Dropped Packets Drop State Drop Cause Drop Flags `, rows[0])
Expand All @@ -153,7 +164,7 @@ func TestFlowTableAdvancedDisplay(t *testing.T) {
assert.Empty(t, rows[3])

// set display to DNS
rows = getRows([]string{dnsDisplay}, []string{noEnrichment})
rows = getRows("DNS", []string{dnsFeature}, noOptions, []string{})

assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port DNS Id DNS Latency DNS RCode DNS Error `, rows[0])
Expand All @@ -162,7 +173,7 @@ func TestFlowTableAdvancedDisplay(t *testing.T) {
assert.Empty(t, rows[3])

// set display to RTT
rows = getRows([]string{rttDisplay}, []string{noEnrichment})
rows = getRows("RTT", []string{rttFeature}, noOptions, []string{})

assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Flow RTT `, rows[0])
Expand All @@ -171,7 +182,7 @@ func TestFlowTableAdvancedDisplay(t *testing.T) {
assert.Empty(t, rows[3])

// set display to NetworkEvents
rows = getRows([]string{networkEventsDisplay}, []string{noEnrichment})
rows = getRows("Network events", []string{networkEventsDisplay}, noOptions, []string{})
assert.Equal(t, 4, len(rows))
assert.Equal(t, `End Time Src IP Src Port Dst IP Dst Port Network Events `, rows[0])
assert.Equal(t, `17:25:28.703000 10.128.0.29 1234 10.129.0.26 5678 hello `, rows[1])
Expand Down
12 changes: 0 additions & 12 deletions cmd/map_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,18 +431,6 @@ func toTimeString(genericMap config.GenericMap, fieldName string) string {
return emptyText
}

func toTitles(strs []string) []string {
titleCaseStrs := []string{}
for _, s := range strs {
titleCaseStrs = append(titleCaseStrs, fmt.Sprintf("%s%s", strings.ToUpper(s[:1]), s[1:]))
}
return titleCaseStrs
}

func toShortTitleStr(strs []string) string {
return replacer.Replace(strings.Join(toTitles(strs), ","))
}

func ToTableColName(id string) string {
name := id
colIndex := slices.IndexFunc(cfg.Columns, func(c *ColumnConfig) bool { return c.ID == id })
Expand Down
82 changes: 82 additions & 0 deletions cmd/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cmd

type option struct {
all []optionItem
current int
}

type optionItem struct {
name string
ids []string
}

var (
allOptions = "All"
noOptions = "None"

// displays
rawDisplay = "Raw"
standardDisplay = "Standard"
pktDropFeature = "pktDrop"
dnsFeature = "dnsTracking"
rttFeature = "flowRTT"
networkEventsDisplay = "networkEvents"
display = option{
all: []optionItem{
// exclusive displays
{name: rawDisplay},
{name: standardDisplay},
// per feature displays
{name: "Packet drops", ids: []string{pktDropFeature}},
{name: "DNS", ids: []string{dnsFeature}},
{name: "RTT", ids: []string{rttFeature}},
{name: "Network events", ids: []string{networkEventsDisplay}},
// all features display
{name: allOptions, ids: []string{pktDropFeature, dnsFeature, rttFeature, networkEventsDisplay}},
},
// standard display by default
current: 1,
}

// enrichments
enrichment = option{
all: []optionItem{
// no enrichment
{name: noOptions},
// per field enrichments
{name: "Cluster", ids: []string{"ClusterName"}},
{name: "Zone", ids: []string{"SrcZone", "DstZone"}},
{name: "Host", ids: []string{"SrcK8S_HostIP", "DstK8S_HostIP", "SrcK8S_HostName", "DstK8S_HostName", "FlowDirection"}},
{name: "Namespace", ids: []string{"SrcK8S_Namespace", "DstK8S_Namespace"}},
{name: "Owner", ids: []string{"SrcK8S_OwnerType", "DstK8S_OwnerType", "SrcK8S_OwnerName", "DstK8S_OwnerName", "SrcK8S_Namespace", "DstK8S_Namespace"}},
{name: "Resource", ids: []string{"SrcK8S_Type", "DstK8S_Type", "SrcK8S_Name", "DstK8S_Name", "SrcK8S_Namespace", "DstK8S_Namespace"}},
{name: "SubnetLabel", ids: []string{"SrcSubnetLabel", "DstSubnetLabel"}},
// all fields
{name: allOptions, ids: []string{
"ClusterName",
"SrcZone", "DstZone",
"SrcK8S_HostIP", "DstK8S_HostIP", "SrcK8S_HostName", "DstK8S_HostName",
"SrcK8S_Namespace", "DstK8S_Namespace",
"SrcK8S_OwnerType", "DstK8S_OwnerType", "SrcK8S_OwnerName", "DstK8S_OwnerName",
"SrcK8S_Type", "DstK8S_Type", "SrcK8S_Name", "DstK8S_Name",
"SrcSubnetLabel", "DstSubnetLabel",
}},
},
// resource enrichment by default
current: 6,
}
)

func (opt *option) getCurrentItem() optionItem {
return opt.all[opt.current]
}

func (opt *option) prev() {
opt.current += len(opt.all) - 1
opt.current %= len(opt.all)
}

func (opt *option) next() {
opt.current++
opt.current %= len(opt.all)
}
Loading