outlet/core: accept flows where interface names are missing

This matches the pre-2.0.0 behavior. This may not be a bug, but the idea
is that one interface is enough to be valid. Some implementations seem
to use bogus indexes.

See https://github.com/akvorado/akvorado/discussions/1998#discussioncomment-14992940
This commit is contained in:
Vincent Bernat
2025-11-17 21:06:12 +01:00
parent 12d6cfcc1c
commit 6e1d3c30d2
3 changed files with 45 additions and 16 deletions

View File

@@ -17,6 +17,7 @@ identified with a specific icon:
false by default)
- 🩹 *outlet*: provide additional gracetime for a worker to send to ClickHouse
- 🩹 *outlet*: enhance scaling up and down workers to avoid hysteresis
- 🩹 *outlet*: accept flows where interface names or descriptions are missing
- 🩹 *docker*: update Traefik to 3.6.1 (for compatibility with Docker Engine 29)
- 🌱 *common*: enable block and mutex profiling
- 🌱 *config*: rename `verify` to `skip-verify` in TLS configurations for

View File

@@ -38,10 +38,7 @@ func (w *worker) enrichFlow(exporterIP netip.Addr, exporterStr string) bool {
if flow.InIf != 0 {
answer := c.d.Metadata.Lookup(t, exporterIP, uint(flow.InIf))
if !answer.Found {
c.metrics.flowsErrors.WithLabelValues(exporterStr, "metadata cache miss").Inc()
skip = true
} else {
if answer.Found {
flowExporterName = answer.Exporter.Name
expClassification.Region = answer.Exporter.Region
expClassification.Role = answer.Exporter.Role
@@ -61,14 +58,7 @@ func (w *worker) enrichFlow(exporterIP netip.Addr, exporterStr string) bool {
if flow.OutIf != 0 {
answer := c.d.Metadata.Lookup(t, exporterIP, uint(flow.OutIf))
if !answer.Found {
// Only register a cache miss if we don't have one.
// TODO: maybe we could do one SNMP query for both interfaces.
if !skip {
c.metrics.flowsErrors.WithLabelValues(exporterStr, "metadata cache miss").Inc()
skip = true
}
} else {
if answer.Found {
flowExporterName = answer.Exporter.Name
expClassification.Region = answer.Exporter.Region
expClassification.Role = answer.Exporter.Role
@@ -90,6 +80,9 @@ func (w *worker) enrichFlow(exporterIP netip.Addr, exporterStr string) bool {
if flow.OutIf == 0 && flow.InIf == 0 {
c.metrics.flowsErrors.WithLabelValues(exporterStr, "input and output interfaces missing").Inc()
skip = true
} else if flowExporterName == "" {
c.metrics.flowsErrors.WithLabelValues(exporterStr, "metadata cache miss").Inc()
skip = true
}
if samplingRate, ok := c.config.OverrideSamplingRate.Lookup(exporterIP); ok && samplingRate > 0 {

View File

@@ -7,6 +7,7 @@ import (
"bytes"
"encoding/gob"
"fmt"
"maps"
"net/netip"
"sync"
"testing"
@@ -31,10 +32,11 @@ import (
func TestEnrich(t *testing.T) {
cases := []struct {
Name string
Configuration gin.H
InputFlow func() *schema.FlowMessage
OutputFlow *schema.FlowMessage
Name string
Configuration gin.H
InputFlow func() *schema.FlowMessage
OutputFlow *schema.FlowMessage
ExpectedMetrics map[string]string
}{
{
Name: "no rule",
@@ -583,6 +585,38 @@ ClassifyProviderRegex(Interface.Description, "^Transit: ([^ ]+)", "$1")`,
},
},
},
{
Name: "flow with missing interfaces",
Configuration: gin.H{},
InputFlow: func() *schema.FlowMessage {
return &schema.FlowMessage{
SamplingRate: 1000,
ExporterAddress: netip.MustParseAddr("::ffff:192.0.2.142"),
InIf: 0,
OutIf: 0,
}
},
OutputFlow: nil,
ExpectedMetrics: map[string]string{
`flows_errors_total{error="input and output interfaces missing",exporter="192.0.2.142"}`: "1",
},
},
{
Name: "flow with metadata cache miss",
Configuration: gin.H{},
InputFlow: func() *schema.FlowMessage {
return &schema.FlowMessage{
SamplingRate: 1000,
ExporterAddress: netip.MustParseAddr("::ffff:192.0.2.142"),
InIf: 999,
OutIf: 0,
}
},
OutputFlow: nil,
ExpectedMetrics: map[string]string{
`flows_errors_total{error="metadata cache miss",exporter="192.0.2.142"}`: "1",
},
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
@@ -684,6 +718,7 @@ ClassifyProviderRegex(Interface.Description, "^Transit: ([^ ]+)", "$1")`,
if tc.OutputFlow != nil {
expectedMetrics[`forwarded_flows_total{exporter="192.0.2.142"}`] = "1"
}
maps.Copy(expectedMetrics, tc.ExpectedMetrics)
if diff := helpers.Diff(gotMetrics, expectedMetrics); diff != "" {
t.Fatalf("Metrics (-got, +want):\n%s", diff)
}