Skip to content

Conversation

@shrouti1995
Copy link

@shrouti1995 shrouti1995 commented Aug 13, 2025

This PR implements a new connection tracking monitoring system that leverages netlink to directly access kernel conntrack data. The implementation provides zone-based monitoring capabilities, allowing for more granular network traffic analysis

  • Tested with 1 M and 2M conntracks created in s2r7node11.

@armando-migliaccio
Copy link
Contributor

_ No description provided. _

Please provide a descriptive commit message.

@shrouti1995
Copy link
Author

_ No description provided. _

Please provide a descriptive commit message.

sure! for now updated the description

@shrouti1995 shrouti1995 requested a review from Copilot August 13, 2025 10:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a new connection tracking monitoring system that leverages netlink to directly access kernel conntrack data. The system provides zone-based monitoring capabilities for granular network traffic analysis and DDoS detection.

  • Adds a new ConntrackService that uses netlink to query kernel conntrack entries
  • Integrates the conntrack service into the main OVS client with proper lifecycle management
  • Updates dependencies to support the new conntrack functionality

Reviewed Changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.

File Description
ovsnl/conntrack.go New service implementing conntrack entry retrieval and conversion from kernel data
ovsnl/client.go Integration of ConntrackService into main client with initialization and cleanup
go.mod Dependency updates including ti-mo/conntrack library and Go version upgrade

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

// Start dump in goroutine
go func() {
defer close(flowChan)
flows, err := s.client.Dump(nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

I am curious how well Dump scales, and whether you should be using DumpFilter or DumpExpect instead (or maybe even some new variant that just counts if needed)
How many entries did you scale to , in your test setup?

Copy link
Author

Choose a reason for hiding this comment

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

I did a POC, did not test yet on heavy traffic. Will check on DumpFilter or DumpExpect as well. Let me get back to you

Copy link
Contributor

Choose a reason for hiding this comment

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

You won't need heavy traffic. Just scale up to like a million or two Conntrack entries and see if it performs well.

Copy link
Author

Choose a reason for hiding this comment

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

updated the code. Checked with 1 million conntrack entries. It is working. The code needs a lot of cleanup. Just a heads up.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's encouraging. While you are at it, could you try the max conntrack limit as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the snapshot from your scaled run, it does indicate the irq plateau for the duration of the run. I am assuming there were no CPU lockup messages from the kernel during this run, correct?

Did you get a chance to optimize the frequency of metrics collection?

On another note, this collection should be controlled with a config knob and we should slow roll this carefully.

Also cc @jcooperdo for another pair of eyes.

Copy link
Author

@shrouti1995 shrouti1995 Sep 23, 2025

Choose a reason for hiding this comment

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

@do-msingh was working on the issue you caught from screenshot. It was not properly doing conntrack count refresh. It looks under control now. As of now I tested seeding conntracks to a specific droplet in a hyperviser. In this screenshot you will see only around 400K jump because for easy testing I kept the timeout 10 mins. But actually created 2.6 Million conntracks only. I will test scenarios like adding timeout for 1 hr, seeding conntracks to multiple droplets (10-20), and see how the system performs. Will keep posted here.

Copy link
Author

@shrouti1995 shrouti1995 Sep 23, 2025

Choose a reason for hiding this comment

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

While testing with 1 hr timeout with 2.6M conntracks created against 1 droplet in a single zone. There are some small discrepencies due to processing delay: ( For example we have missed 12k events to count while running sync for 2.6M conntracks )
I can try to fix it later as an improvement task

Copy link
Author

@shrouti1995 shrouti1995 Sep 26, 2025

Choose a reason for hiding this comment

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

tested with 3 droplets with three different zones each receiving 2.6 mililon events over 4 hrs.
Screenshot 2025-09-30 at 12 35 45 PM

Screenshot 2025-09-30 at 12 36 03 PM Screenshot 2025-09-30 at 12 36 27 PM Screenshot 2025-09-26 at 5 09 14 PM did not see any oom kill error Screenshot 2025-09-26 at 5 09 47 PM

Copy link
Author

@shrouti1995 shrouti1995 Sep 29, 2025

Choose a reason for hiding this comment

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

in the same way when I tested creating conntracks without my changes in openvswitch_exporter the graph looks like above

@shrouti1995 shrouti1995 requested a review from do-msingh August 28, 2025 16:14
@shrouti1995 shrouti1995 marked this pull request as ready for review September 2, 2025 20:00
@shrouti1995
Copy link
Author

The build is failing due to go version in my local machine. In this repository we are using older version. When the code is signed off I will try to install the older version and push it. Kept it like this for now.


// NewZoneMarkAggregator creates a new aggregator with its own listening connection.
func NewZoneMarkAggregator(s *ConntrackService) (*ZoneMarkAggregator, error) {
log.Printf("Creating new conntrack zone mark aggregator...")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you remove these logs?

return nil, fmt.Errorf("failed to create listening connection: %w", err)
}

log.Printf("Successfully created conntrack listening connection")
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

// Start dump in goroutine
go func() {
defer close(flowChan)
flows, err := s.client.Dump(nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

This script would be nice to integrate with chef and maybe export some metrics using node exporter so we can build some dashboards around it. In your tests, could you run at scale for an extended period like a couple of hours and check average CPU util? Do you only see CPU spikes around the time metrics are collected? For how long? Also @jcooper had a suggestion to reduce the frequency of collecting the metrics, or maybe optimizing it to reduce load.
Lastly, can you check dmesg output as well at scale to make sure we are not missing anything?

Co-authored-by: jcooperdo <46059766+jcooperdo@users.noreply.github.com>
Comment on lines 201 to 203
if atomic.LoadInt64(&a.eventCount)%100 == 0 {
runtime.Gosched()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

what is this for?

Copy link
Author

Choose a reason for hiding this comment

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

I was testing with 2.6M conntracks sent to a single vm and also was conducting a test when multiple vms are receiving heavy conntrack. My intention is to tell the scheduler to give up the current goroutine's time slice and let others run. Found out this can be used to improve concurrency and responsiveness in high-throughput or tight-loop scenarios. Doing it for every event will be inefficient so I wanted to have the if condition


// eventWorker consumes events from eventsCh and handles them
func (a *ZoneMarkAggregator) eventWorker(workerID int) {
processedCount := 0
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure i see the value in this if all we're gonna do is just log it out

}

for i := 0; i < eventWorkerCount; i++ {
workerID := i // Capture the loop variable
Copy link
Contributor

Choose a reason for hiding this comment

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

not necessary in the recent versions of go

Comment on lines 152 to 153
if a.eventCount.Load()%100 == 0 {
runtime.Gosched()
Copy link
Contributor

Choose a reason for hiding this comment

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

this still strongly feels like a smell
if we're starting 100 eventWorker goroutines, and it's still not able to handle it, we should look at changing the overall design of this concurrent work, not mess with the scheduling of goroutines with the runtime.

Copy link
Author

Choose a reason for hiding this comment

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

let me test it without this. And post a report here.

Copy link
Author

Choose a reason for hiding this comment

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

as per testing softirq was around 1.3

return fmt.Errorf("failed to create new listening connection: %w", err)
}
a.listenCli = listenCli
return a.startEventListener()
Copy link
Contributor

Choose a reason for hiding this comment

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

if we're gonna start a new event listening loop, we should ensure the previous listening goroutine is canceled and exits cleanly

shrouti1995 and others added 2 commits October 21, 2025 09:44
…mory enhancement

Co-authored-by: Anit Gandhi <agandhi@digitalocean.com>
ovsnl/client.go Outdated

c *genetlink.Conn
c *genetlink.Conn
Agg *ZoneMarkAggregator // lazily initialized
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't see NewZoneMarkAggregator being called to actually set this?

Copy link
Author

Choose a reason for hiding this comment

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

yeah this can be deleted, I have used it in digitalocean/openvswitch_exporter#21 but differently. Removing it.

}
}

a.wg.Wait()
Copy link
Contributor

Choose a reason for hiding this comment

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

how would this work in practice? nothing is telling all the goroutines to exit via stopCh?

Copy link
Author

Choose a reason for hiding this comment

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

added stop for all channels

@shrouti1995
Copy link
Author

we have moved the logic in digitalocean/openvswitch_exporter#21

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.

6 participants