Skip to content

Commit 47ccd8b

Browse files
authored
feat(ct-metrics): BPF implementation (#1102)
# Description BPF implementation for connection tracking metrics. This is the data-plane work mentioned in this comment #1057 (comment) Summary - feature flag enableConntrackMetrics - counters incremented within IFDEF in BPF - counters: packets forward/reply + bytes forward/reply - conntrack metadata includes metrics and is added to packets struct - add/update unit tests for conntrack_linux and packetparser_linux ## Related Issue #806 ## Checklist - [x] I have read the [contributing documentation](https://retina.sh/docs/contributing). - [x] I signed and signed-off the commits (`git commit -S -s ...`). See [this documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) on signing commits. - [x] I have correctly attributed the author(s) of the code. - [x] I have tested the changes locally. - [x] I have followed the project's style guidelines. - [x] I have updated the documentation, if necessary. - [x] I have added tests, if applicable. ## Screenshots (if applicable) or Testing Completed Please add any relevant screenshots or GIFs to showcase the changes made. 1. `enableConntrackMetrics=false` ```sh # bpftool map dump id 994 -j | jq -r .[0] { "key": [ ... ], "value": [ ... ], "formatted": { "key": { ... }, "value": { ... "conntrack_metadata": { "bytes_forward_count": 0, "bytes_reply_count": 0, "packets_forward_count": 0, "packets_reply_count": 0 } } } } ``` 2. `enableConntrackMetrics=true` ```sh # bpftool map dump id 1019 -j | jq -r .[0] { "key": [ ... ], "value": [ ... ], "formatted": { "key": { ... }, "value": { ..., "conntrack_metadata": { "bytes_forward_count": 13440, "bytes_reply_count": 56335, "packets_forward_count": 56, "packets_reply_count": 43 } } } } ``` At userland level I provisionally added a debug statement, just for this test, in `packetparser_linux.go` (without IP and proto translation) ```sh ❯ k logs -n kube-system retina-agent-chvdh | head -n 10 | grep metadata Defaulted container "retina" out of: retina, init-retina (init) ts=2024-12-13T10:37:08.881Z level=debug caller=packetparser/packetparser_linux.go:577 msg="Conntrack metadata" SrcIp=788657162 DstIp=2499867658 SrcPort=19117 DstPort=23313 Proto=6 PacketsForwardCount=73 PacketsReplyCount=83 BytesForwardCount=16068 BytesReplyCount=6936 ts=2024-12-13T10:37:08.881Z level=debug caller=packetparser/packetparser_linux.go:577 msg="Conntrack metadata" SrcIp=788657162 DstIp=2499867658 SrcPort=19117 DstPort=23313 Proto=6 PacketsForwardCount=73 PacketsReplyCount=82 BytesForwardCount=16068 BytesReplyCount=6870 ``` ## Additional Notes Add any additional notes or context about the pull request here. --- Please refer to the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more information on how to contribute to this project.
1 parent 6cb7d34 commit 47ccd8b

File tree

16 files changed

+345
-48
lines changed

16 files changed

+345
-48
lines changed

deploy/hubble/manifests/controller/helm/retina/templates/agent/configmap.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ data:
108108
metricsIntervalDuration: {{ .Values.metricsIntervalDuration }}
109109
enableTelemetry: {{ .Values.enableTelemetry }}
110110
enablePodLevel: {{ .Values.enablePodLevel }}
111+
enableConntrackMetrics: {{ .Values.enableConntrackMetrics }}
111112
remoteContext: {{ .Values.remoteContext }}
112113
enableAnnotations: {{ .Values.enableAnnotations }}
113114
bypassLookupIPOfInterest: {{ .Values.bypassLookupIPOfInterest }}

deploy/legacy/manifests/controller/helm/retina/templates/configmap.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ data:
1919
metricsIntervalDuration: {{ .Values.metricsIntervalDuration }}
2020
enableTelemetry: {{ .Values.enableTelemetry }}
2121
enablePodLevel: {{ .Values.enablePodLevel }}
22+
enableConntrackMetrics: {{ .Values.enableConntrackMetrics }}
2223
remoteContext: {{ .Values.remoteContext }}
2324
enableAnnotations: {{ .Values.enableAnnotations }}
2425
bypassLookupIPOfInterest: {{ .Values.bypassLookupIPOfInterest }}

deploy/legacy/manifests/controller/helm/retina/values.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ image:
3535
# Overrides the image tag whose default is the chart appVersion.
3636
tag: "v0.0.2"
3737

38+
enableConntrackMetrics: false
3839
enablePodLevel: false
3940
remoteContext: false
4041
enableAnnotations: false

pkg/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Config struct {
6161
EnableTelemetry bool `yaml:"enableTelemetry"`
6262
EnableRetinaEndpoint bool `yaml:"enableRetinaEndpoint"`
6363
EnablePodLevel bool `yaml:"enablePodLevel"`
64+
EnableConntrackMetrics bool `yaml:"enableConntrackMetrics"`
6465
RemoteContext bool `yaml:"remoteContext"`
6566
EnableAnnotations bool `yaml:"enableAnnotations"`
6667
BypassLookupIPOfInterest bool `yaml:"bypassLookupIPOfInterest"`

pkg/plugin/conntrack/_cprog/conntrack.c

+66-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "compiler.h"
88
#include "bpf_helpers.h"
99
#include "conntrack.h"
10+
#include "dynamic.h"
1011

1112
struct tcpmetadata {
1213
__u32 seq; // TCP sequence number
@@ -15,6 +16,20 @@ struct tcpmetadata {
1516
__u32 tsecr; // TCP timestamp echo reply
1617
};
1718

19+
struct conntrackmetadata {
20+
/*
21+
bytes_*_count indicates the number of bytes sent and received in the forward and reply direction.
22+
These values will be based on the conntrack entry.
23+
*/
24+
__u64 bytes_forward_count;
25+
__u64 bytes_reply_count;
26+
/*
27+
packets_*_count indicates the number of packets sent and received in the forward and reply direction.
28+
These values will be based on the conntrack entry.
29+
*/
30+
__u32 packets_forward_count;
31+
__u32 packets_reply_count;
32+
};
1833

1934
struct packet
2035
{
@@ -30,6 +45,7 @@ struct packet
3045
__u8 proto;
3146
__u8 flags; // For TCP packets, this is the TCP flags. For UDP packets, this is will always be 1 for conntrack purposes.
3247
bool is_reply;
48+
struct conntrackmetadata conntrack_metadata;
3349
};
3450

3551

@@ -68,6 +84,7 @@ struct ct_entry {
6884
* before retina deployment and the SYN packet was not captured.
6985
*/
7086
bool is_direction_unknown;
87+
struct conntrackmetadata conntrack_metadata;
7188
};
7289

7390
struct {
@@ -110,11 +127,11 @@ static __always_inline __u8 _ct_get_traffic_direction(__u8 observation_point) {
110127

111128
/**
112129
* Create a new TCP connection.
130+
* @arg *p pointer to the packet to be processed.
113131
* @arg key The key to be used to create the new connection.
114-
* @arg flags The flags of the packet.
115132
* @arg observation_point The point in the network stack where the packet is observed.
116133
*/
117-
static __always_inline bool _ct_create_new_tcp_connection(struct ct_v4_key key, __u8 flags, __u8 observation_point) {
134+
static __always_inline bool _ct_create_new_tcp_connection(struct packet *p, struct ct_v4_key key, __u8 observation_point) {
118135
struct ct_entry new_value;
119136
__builtin_memset(&new_value, 0, sizeof(struct ct_entry));
120137
__u64 now = bpf_mono_now();
@@ -123,9 +140,20 @@ static __always_inline bool _ct_create_new_tcp_connection(struct ct_v4_key key,
123140
return false;
124141
}
125142
new_value.eviction_time = now + CT_SYN_TIMEOUT;
126-
new_value.flags_seen_tx_dir = flags;
143+
new_value.flags_seen_tx_dir = p->flags;
127144
new_value.is_direction_unknown = false;
128145
new_value.traffic_direction = _ct_get_traffic_direction(observation_point);
146+
147+
#ifdef ENABLE_CONNTRACK_METRICS
148+
new_value.conntrack_metadata.packets_forward_count = 1;
149+
new_value.conntrack_metadata.bytes_forward_count = p->bytes;
150+
// Update initial conntrack metadata for the connection.
151+
__builtin_memcpy(&p->conntrack_metadata, &new_value.conntrack_metadata, sizeof(struct conntrackmetadata));
152+
#endif // ENABLE_CONNTRACK_METRICS
153+
154+
// Update packet
155+
p->is_reply = false;
156+
p->traffic_direction = new_value.traffic_direction;
129157
bpf_map_update_elem(&retina_conntrack, &key, &new_value, BPF_ANY);
130158
return true;
131159
}
@@ -148,10 +176,17 @@ static __always_inline bool _ct_handle_udp_connection(struct packet *p, struct c
148176
new_value.flags_seen_tx_dir = p->flags;
149177
new_value.last_report_tx_dir = now;
150178
new_value.traffic_direction = _ct_get_traffic_direction(observation_point);
151-
bpf_map_update_elem(&retina_conntrack, &key, &new_value, BPF_ANY);
179+
#ifdef ENABLE_CONNTRACK_METRICS
180+
new_value.conntrack_metadata.packets_forward_count = 1;
181+
new_value.conntrack_metadata.bytes_forward_count = p->bytes;
182+
// Update packet's conntrack metadata.
183+
__builtin_memcpy(&p->conntrack_metadata, &new_value.conntrack_metadata, sizeof(struct conntrackmetadata));;
184+
#endif // ENABLE_CONNTRACK_METRICS
185+
152186
// Update packet
153187
p->is_reply = false;
154188
p->traffic_direction = new_value.traffic_direction;
189+
bpf_map_update_elem(&retina_conntrack, &key, &new_value, BPF_ANY);
155190
return true;
156191
}
157192

@@ -165,11 +200,8 @@ static __always_inline bool _ct_handle_udp_connection(struct packet *p, struct c
165200
static __always_inline bool _ct_handle_tcp_connection(struct packet *p, struct ct_v4_key key, struct ct_v4_key reverse_key, __u8 observation_point) {
166201
// Check if the packet is a SYN packet.
167202
if (p->flags & TCP_SYN) {
168-
// Update packet accordingly.
169-
p->is_reply = false;
170-
p->traffic_direction = _ct_get_traffic_direction(observation_point);
171203
// Create a new connection with a timeout of CT_SYN_TIMEOUT.
172-
return _ct_create_new_tcp_connection(key, p->flags, observation_point);
204+
return _ct_create_new_tcp_connection(p, key, observation_point);
173205
}
174206

175207
// The packet is not a SYN packet and the connection corresponding to this packet is not found.
@@ -193,13 +225,25 @@ static __always_inline bool _ct_handle_tcp_connection(struct packet *p, struct c
193225
p->is_reply = true;
194226
new_value.flags_seen_rx_dir = p->flags;
195227
new_value.last_report_rx_dir = now;
228+
#ifdef ENABLE_CONNTRACK_METRICS
229+
new_value.conntrack_metadata.bytes_reply_count = p->bytes;
230+
new_value.conntrack_metadata.packets_reply_count = 1;
231+
#endif // ENABLE_CONNTRACK_METRICS
196232
bpf_map_update_elem(&retina_conntrack, &reverse_key, &new_value, BPF_ANY);
197233
} else { // Otherwise, the packet is considered as a packet in the send direction.
198234
p->is_reply = false;
199235
new_value.flags_seen_tx_dir = p->flags;
200236
new_value.last_report_tx_dir = now;
237+
#ifdef ENABLE_CONNTRACK_METRICS
238+
new_value.conntrack_metadata.bytes_forward_count = p->bytes;
239+
new_value.conntrack_metadata.packets_forward_count = 1;
240+
#endif // ENABLE_CONNTRACK_METRICS
201241
bpf_map_update_elem(&retina_conntrack, &key, &new_value, BPF_ANY);
202242
}
243+
#ifdef ENABLE_CONNTRACK_METRICS
244+
// Update packet's conntrack metadata.
245+
__builtin_memcpy(&p->conntrack_metadata, &new_value.conntrack_metadata, sizeof(struct conntrackmetadata));
246+
#endif // ENABLE_CONNTRACK_METRICS
203247
return true;
204248
}
205249

@@ -318,6 +362,13 @@ static __always_inline __attribute__((unused)) bool ct_process_packet(struct pac
318362
// Update the packet accordingly.
319363
p->is_reply = false;
320364
p->traffic_direction = entry->traffic_direction;
365+
#ifdef ENABLE_CONNTRACK_METRICS
366+
// Update packet count and bytes count on conntrack entry.
367+
WRITE_ONCE(entry->conntrack_metadata.packets_forward_count, READ_ONCE(entry->conntrack_metadata.packets_forward_count) + 1);
368+
WRITE_ONCE(entry->conntrack_metadata.bytes_forward_count, READ_ONCE(entry->conntrack_metadata.bytes_forward_count) + p->bytes);
369+
// Update packet's conntract metadata.
370+
__builtin_memcpy(&p->conntrack_metadata, &entry->conntrack_metadata, sizeof(struct conntrackmetadata));
371+
#endif // ENABLE_CONNTRACK_METRICS
321372
return _ct_should_report_packet(entry, p->flags, CT_PACKET_DIR_TX, &key);
322373
}
323374

@@ -333,6 +384,13 @@ static __always_inline __attribute__((unused)) bool ct_process_packet(struct pac
333384
// Update the packet accordingly.
334385
p->is_reply = true;
335386
p->traffic_direction = entry->traffic_direction;
387+
#ifdef ENABLE_CONNTRACK_METRICS
388+
// Update packet count and bytes count on conntrack entry.
389+
WRITE_ONCE(entry->conntrack_metadata.packets_reply_count, READ_ONCE(entry->conntrack_metadata.packets_reply_count) + 1);
390+
WRITE_ONCE(entry->conntrack_metadata.bytes_reply_count, READ_ONCE(entry->conntrack_metadata.bytes_reply_count) + p->bytes);
391+
// Update packet's conntract metadata.
392+
__builtin_memcpy(&p->conntrack_metadata, &entry->conntrack_metadata, sizeof(struct conntrackmetadata));
393+
#endif // ENABLE_CONNTRACK_METRICS
336394
return _ct_should_report_packet(entry, p->flags, CT_PACKET_DIR_RX, &reverse_key);
337395
}
338396

pkg/plugin/conntrack/_cprog/dynamic.h

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Place holder header file that will be replaced by the actual header file during runtime
2+
// DO NOT DELETE

pkg/plugin/conntrack/conntrack_bpfel_arm64.go

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/plugin/conntrack/conntrack_bpfel_x86.go

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/plugin/conntrack/conntrack_linux.go

+25
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ package conntrack
66

77
import (
88
"context"
9+
"fmt"
10+
"path"
11+
"runtime"
912
"time"
1013

1114
"github.com/cilium/ebpf"
1215
"github.com/cilium/ebpf/rlimit"
1316
"github.com/microsoft/retina/internal/ktime"
17+
"github.com/microsoft/retina/pkg/loader"
1418
"github.com/microsoft/retina/pkg/log"
1519
plugincommon "github.com/microsoft/retina/pkg/plugin/common"
1620
_ "github.com/microsoft/retina/pkg/plugin/conntrack/_cprog" // nolint // This is needed so cprog is included when vendoring
@@ -66,6 +70,27 @@ func New() (*Conntrack, error) {
6670
return ct, nil
6771
}
6872

73+
// Build dynamic header path
74+
func BuildDynamicHeaderPath() string {
75+
// Get absolute path to this file during runtime.
76+
_, filename, _, ok := runtime.Caller(0)
77+
if !ok {
78+
return ""
79+
}
80+
currDir := path.Dir(filename)
81+
return fmt.Sprintf("%s/%s/%s", currDir, bpfSourceDir, dynamicHeaderFileName)
82+
}
83+
84+
// Generate dynamic header file for conntrack eBPF program.
85+
func GenerateDynamic(ctx context.Context, dynamicHeaderPath string, conntrackMetrics int) error {
86+
st := fmt.Sprintf("#define ENABLE_CONNTRACK_METRICS %d\n", conntrackMetrics)
87+
err := loader.WriteFile(ctx, dynamicHeaderPath, st)
88+
if err != nil {
89+
return errors.Wrap(err, "failed to write conntrack dynamic header")
90+
}
91+
return nil
92+
}
93+
6994
// Run starts the Conntrack garbage collection loop.
7095
func (ct *Conntrack) Run(ctx context.Context) error {
7196
ticker := time.NewTicker(ct.gcFrequency)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package conntrack
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path"
8+
"runtime"
9+
"testing"
10+
)
11+
12+
func TestBuildDynamicHeaderPath(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
expectedPath string
16+
}{
17+
{
18+
name: "ExpectedPath",
19+
expectedPath: fmt.Sprintf("%s/%s/%s", path.Dir(getCurrentFilePath(t)), bpfSourceDir, dynamicHeaderFileName),
20+
},
21+
}
22+
23+
for _, tt := range tests {
24+
t.Run(tt.name, func(t *testing.T) {
25+
actualPath := BuildDynamicHeaderPath()
26+
if actualPath != tt.expectedPath {
27+
t.Errorf("unexpected dynamic header path: got %q, want %q", actualPath, tt.expectedPath)
28+
}
29+
})
30+
}
31+
}
32+
33+
func TestGenerateDynamic(t *testing.T) {
34+
tests := []struct {
35+
name string
36+
conntrackMetrics int
37+
expectedContents string
38+
}{
39+
{
40+
name: "ConntrackMetricsEnabled",
41+
conntrackMetrics: 1,
42+
expectedContents: "#define ENABLE_CONNTRACK_METRICS 1\n",
43+
},
44+
{
45+
name: "ConntrackMetricsDisabled",
46+
conntrackMetrics: 0,
47+
expectedContents: "#define ENABLE_CONNTRACK_METRICS 0\n",
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.name, func(t *testing.T) {
53+
// Create a temporary directory
54+
tempDir, err := os.MkdirTemp("", "conntrack_test")
55+
if err != nil {
56+
t.Fatalf("failed to create temp directory: %v", err)
57+
}
58+
// Clean up the temporary directory after the test completes
59+
defer os.RemoveAll(tempDir)
60+
61+
// Override the dynamicHeaderPath to use the temporary directory
62+
dynamicHeaderPath := path.Join(tempDir, dynamicHeaderFileName)
63+
64+
// Call the GenerateDynamic function and check if it returns an error.
65+
ctx := context.Background()
66+
if err = GenerateDynamic(ctx, dynamicHeaderPath, tt.conntrackMetrics); err != nil {
67+
t.Fatalf("failed to generate dynamic header: %v", err)
68+
}
69+
70+
// Verify that the dynamic header file was created in the expected location and contains the expected contents.
71+
if _, err = os.Stat(dynamicHeaderPath); os.IsNotExist(err) {
72+
t.Fatalf("dynamic header file does not exist: %v", err)
73+
}
74+
75+
actualContents, err := os.ReadFile(dynamicHeaderPath)
76+
if err != nil {
77+
t.Fatalf("failed to read dynamic header file: %v", err)
78+
}
79+
if string(actualContents) != tt.expectedContents {
80+
t.Errorf("unexpected dynamic header file contents: got %q, want %q", string(actualContents), tt.expectedContents)
81+
}
82+
})
83+
}
84+
}
85+
86+
func getCurrentFilePath(t *testing.T) string {
87+
_, filename, _, ok := runtime.Caller(1)
88+
if !ok {
89+
t.Fatal("failed to determine test file path")
90+
}
91+
return filename
92+
}

pkg/plugin/conntrack/types_linux.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import (
99
)
1010

1111
const (
12-
defaultGCFrequency = 15 * time.Second
12+
defaultGCFrequency = 15 * time.Second
13+
bpfSourceDir = "_cprog"
14+
bpfSourceFileName = "conntrack.c"
15+
dynamicHeaderFileName = "dynamic.h"
1316
)
1417

1518
type Conntrack struct {

pkg/plugin/packetparser/_cprog/packetparser.c

+6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ static void parse(struct __sk_buff *skb, __u8 obs)
201201
return;
202202
}
203203

204+
#ifdef ENABLE_CONNTRACK_METRICS
205+
// Initialize conntrack metadata in packet struct.
206+
struct conntrackmetadata conntrack_metadata;
207+
__builtin_memset(&conntrack_metadata, 0, sizeof(conntrack_metadata));
208+
p.conntrack_metadata = conntrack_metadata;
209+
#endif // ENABLE_CONNTRACK_METRICS
204210

205211
// Process the packet in ct
206212
bool report __attribute__((unused));

0 commit comments

Comments
 (0)