Skip to content

feat(metrics): measure protocol detection accuracy across all protocols - #322

Open
mayankpande88 wants to merge 1 commit into
mainfrom
feat/protocol-detection-accuracy
Open

feat(metrics): measure protocol detection accuracy across all protocols#322
mayankpande88 wants to merge 1 commit into
mainfrom
feat/protocol-detection-accuracy

Conversation

@mayankpande88

Copy link
Copy Markdown
Contributor

Why

Detection accuracy is only observable for HTTP/2 today, via the invalid-frame ratio — and only because that ratio happened to be instrumented while chasing a specific bug.

That bug turned out to be a weak byte-pattern heuristic whose verdict is cached per connection, so one false positive poisoned an HTTPS/1.1 connection permanently. External HTTP/2 detection accuracy was roughly 16% before the fix and is 95% after.

Every other protocol is detected the same way. The ClickHouse check is three bytes; each protocol caches its verdict identically. The same failure mode is possible for Postgres, Redis, MySQL and Zookeeper and is currently unmeasured. There is no basis for assuming HTTP/2 was unique rather than simply the one we happened to investigate.

What this adds

node_agent_protocol_reclassified_total{protocol}
node_agent_connections_parsed_total{protocol}

A reclassification is a definitive misdetection: eBPF decided the protocol, and the parser then refused the payload parseFailThreshold times consecutively. trackParseFail already computes that verdict — it just was not counted.

Misdetection rate per protocol:

rate(node_agent_protocol_reclassified_total[1h])
/ (rate(node_agent_protocol_reclassified_total[1h]) + rate(node_agent_connections_parsed_total[1h]))

Per-connection, not per-event

Both counters increment once per connection — reclassified at the threshold, parsed on first success.

This is deliberate. The HTTP/2 invalid-frame ratio is per-event, so a single misdetected connection emitting thousands of invalid frames dominates it. That made the ratio a poor accuracy measure even though it was useful for spotting the bug. A per-connection rate answers the actual question: what fraction of connections did we tag wrongly.

trackParseOK replaces the bare parseFailCount = 0 resets at the five success sites, so the denominator is recorded everywhere the numerator can be.

Testing

gofmt, GOOS=linux gopls check clean; full CI suite passes. Measurement only — no behaviour change beyond recording the reset that already happened.

Detection accuracy was only observable for HTTP/2, via the invalid-frame ratio,
and only because that ratio happened to be instrumented while chasing a specific
bug. That bug turned out to be a weak byte-pattern heuristic whose verdict was
cached per connection, so a single false positive poisoned an HTTPS/1.1
connection permanently.

Every other protocol is detected the same way. The ClickHouse check is three
bytes, and each protocol caches its verdict identically, so the same failure
mode is possible for Postgres, Redis, MySQL and Zookeeper and is currently
unmeasured. There is no basis for assuming HTTP/2 was unique rather than merely
the one investigated.

  node_agent_protocol_reclassified_total{protocol}
  node_agent_connections_parsed_total{protocol}

A reclassification is a definitive misdetection: eBPF decided the protocol and
the parser then refused the payload parseFailThreshold times in a row. Both
counters are incremented once per connection — reclassified at the threshold,
parsed on first success — so the ratio is a per-connection misdetection rate
rather than a per-event one, which is what the HTTP/2 invalid-frame ratio got
wrong: one bad connection emitting thousands of invalid frames dominated it.

trackParseOK replaces the bare parseFailCount resets at the five success sites,
so the denominator is recorded wherever the numerator can be.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces tracking for protocol misdetection rates by adding two new Prometheus metrics, ProtocolReclassifiedTotal and ConnectionsParsedTotal, and updating connection parsing logic to record successful and failed parses per connection. The feedback suggests adding a nil check for the conn parameter in the new trackParseOK function to prevent potential runtime panics.

Comment thread containers/container.go
Comment on lines +1512 to +1513
func (c *Container) trackParseOK(conn *ActiveConnection, proto l7.Protocol) {
if !conn.parseSucceeded {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To adhere to defensive programming best practices, we should ensure that 'conn' is not 'nil' before accessing its properties ('parseSucceeded' and 'parseFailCount'). Adding a nil check prevents potential runtime panics if this helper is ever called with a nil connection pointer.

func (c *Container) trackParseOK(conn *ActiveConnection, proto l7.Protocol) {
	if conn == nil {
		return
	}
	if !conn.parseSucceeded {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declining this one, with reasoning.

conn cannot be nil at any trackParseOK call site. All five sit inside the protocol switch in onL7RequestWithResult, which is reached only after conn has been dereferenced repeatedly — conn.DestinationKey, conn.srcWorkload, conn.Timestamp — so a nil conn would have panicked well before this point.

The adjacent trackParseFail, which this mirrors and which has existed for some time, has no nil check either. Adding one to only the new function would be inconsistent, and adding a guard that cannot fire suggests the invariant is uncertain when it is not — it makes the code marginally harder to reason about rather than easier.

If nil-safety here is wanted as a policy, it should go on both functions in a separate change, so the reasoning applies uniformly rather than to whichever function happened to be touched most recently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant