Skip to content

Commit d39e240

Browse files
committed
Quic packet tracker inspired by TLS tracker
Signed-off-by: Mohamed S. Mahmoud <mmahmoud2201@gmail.com>
1 parent 2903ef3 commit d39e240

25 files changed

+506
-70
lines changed

.mk/bc.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ define MAPS
4242
"filter_map":"lpm_trie",
4343
"peer_filter_map":"lpm_trie",
4444
"ipsec_ingress_map":"hash",
45-
"ipsec_egress_map":"hash"
45+
"ipsec_egress_map":"hash",
46+
"quic_flows":"per_cpu_hash"
4647
}
4748
endef
4849

bpf/configs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ volatile const u8 enable_network_events_monitoring = 0;
1515
volatile const u8 network_events_monitoring_groupid = 0;
1616
volatile const u8 enable_pkt_translation_tracking = 0;
1717
volatile const u8 enable_ipsec = 0;
18+
volatile const u8 enable_quic_tracking = 0;
1819
#endif //__CONFIGS_H__

bpf/flows.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
*/
5858
#include "ipsec.h"
5959

60+
/*
61+
* Defines quic tracker
62+
*/
63+
#include "quic_tracker.h"
64+
6065
// return 0 on success, 1 if capacity reached
6166
static __always_inline int add_observed_intf(flow_metrics *value, pkt_info *pkt, u32 if_index,
6267
u8 direction) {
@@ -179,6 +184,10 @@ static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
179184
if (enable_dns_tracking) {
180185
dns_errno = track_dns_packet(skb, &pkt);
181186
}
187+
if (enable_quic_tracking) {
188+
track_quic_packet(skb, &pkt, eth_protocol, direction, len);
189+
}
190+
182191
flow_metrics *aggregate_flow = (flow_metrics *)bpf_map_lookup_elem(&aggregated_flows, &id);
183192
if (aggregate_flow != NULL) {
184193
update_existing_flow(aggregate_flow, &pkt, len, flow_sampling, skb->ifindex, direction);

bpf/maps_definition.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,14 @@ struct {
137137
__uint(pinning, LIBBPF_PIN_BY_NAME);
138138
} ipsec_egress_map SEC(".maps");
139139

140+
// QUIC flow tracking map - keyed by flow_id (like other flow maps)
141+
struct {
142+
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
143+
__type(key, flow_id);
144+
__type(value, quic_metrics);
145+
__uint(max_entries, 1 << 16);
146+
__uint(map_flags, BPF_F_NO_PREALLOC);
147+
__uint(pinning, LIBBPF_PIN_BY_NAME);
148+
} quic_flows SEC(".maps");
149+
140150
#endif //__MAPS_DEFINITION_H__

bpf/quic_tracker.h

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* QUIC Flow Tracker - Kernel-observed metrics using QUIC Invariants (RFC 8999)
3+
* Works with unmodified QUIC implementations (quiche, etc.)
4+
*
5+
* UDP Packet with QUIC:
6+
* +------------------+------------------+------------------+------------------+
7+
* | Ethernet | IP Header | UDP Header | QUIC Payload |
8+
* | 14 bytes | 20/40 bytes | 8 bytes | |
9+
* +------------------+------------------+------------------+------------------+
10+
*
11+
* UDP Header (8 bytes):
12+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13+
* | Source Port | Destination Port |
14+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15+
* | Length | Checksum |
16+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17+
*
18+
* QUIC Long Header (handshake):
19+
* +-+-+-+-+-+-+-+-+
20+
* |1|1| Type |Res| First byte: Form=1 (long), Fixed=1, Type (2 bits)
21+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22+
* | Version (32) |
23+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24+
* | DCID Len (8) | Destination Connection ID |
25+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26+
* | SCID Len (8) | Source Connection ID |
27+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28+
* | [Type-Specific Fields...] |
29+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30+
*
31+
* QUIC Short Header (post-handshake):
32+
* +-+-+-+-+-+-+-+-+
33+
* |0|1|S|R|R|K|P P| First byte: Form=0 (short), Fixed=1
34+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35+
* | Destination Connection ID |
36+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37+
* | [Encrypted Payload...] |
38+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39+
*/
40+
41+
#ifndef __QUIC_TRACKER_H__
42+
#define __QUIC_TRACKER_H__
43+
44+
#include "utils.h"
45+
#include "maps_definition.h"
46+
47+
#define QUIC_PORT 443
48+
#define QUIC_LONG_HEADER 0x80
49+
#define QUIC_FIXED_BIT 0x40
50+
#define QUIC_MIN_PACKET_SIZE 21
51+
52+
// Parse QUIC header, returns: 0=not QUIC, 1=short header, 2=long header
53+
// If long header, version is set
54+
static __always_inline int parse_quic_header(struct __sk_buff *skb, u32 offset, u32 *version) {
55+
u8 first_byte;
56+
if (bpf_skb_load_bytes(skb, offset, &first_byte, 1) < 0)
57+
return 0;
58+
59+
// QUIC packets must have fixed bit set
60+
if (!(first_byte & QUIC_FIXED_BIT))
61+
return 0;
62+
63+
if (first_byte & QUIC_LONG_HEADER) {
64+
// Long header: read version (bytes 1-4)
65+
u32 ver;
66+
if (bpf_skb_load_bytes(skb, offset + 1, &ver, 4) < 0)
67+
return 0;
68+
*version = bpf_ntohl(ver);
69+
return 2; // long header
70+
}
71+
72+
return 1; // short header
73+
}
74+
75+
static __always_inline int track_quic_packet(struct __sk_buff *skb, pkt_info *pkt, u16 eth_protocol,
76+
u8 direction, u32 len) {
77+
if (pkt->id->transport_protocol != IPPROTO_UDP)
78+
return 0;
79+
80+
// Only track QUIC port
81+
if (pkt->id->dst_port != QUIC_PORT && pkt->id->src_port != QUIC_PORT)
82+
return 0;
83+
84+
struct udphdr *udp = (struct udphdr *)pkt->l4_hdr;
85+
if (!udp)
86+
return 0;
87+
88+
u16 udp_len = bpf_ntohs(udp->len);
89+
if (udp_len < sizeof(struct udphdr) + QUIC_MIN_PACKET_SIZE)
90+
return 0;
91+
92+
u32 quic_offset = (u32)((long)udp - (long)skb->data) + sizeof(struct udphdr);
93+
94+
// Parse QUIC header
95+
u32 version = 0;
96+
int hdr_type = parse_quic_header(skb, quic_offset, &version);
97+
if (hdr_type == 0)
98+
return 0;
99+
100+
u64 now = pkt->current_ts;
101+
flow_id *id = pkt->id;
102+
103+
// Lookup or create QUIC metrics
104+
quic_metrics *flow = bpf_map_lookup_elem(&quic_flows, id);
105+
if (flow) {
106+
flow->end_mono_time_ts = now;
107+
flow->packets++;
108+
flow->bytes += len;
109+
if (hdr_type == 2) {
110+
flow->seen_long_hdr = 1;
111+
if (version != 0)
112+
flow->version = version;
113+
} else {
114+
flow->seen_short_hdr = 1;
115+
}
116+
} else {
117+
quic_metrics new_flow = {
118+
.start_mono_time_ts = now,
119+
.end_mono_time_ts = now,
120+
.bytes = len,
121+
.packets = 1,
122+
.version = version,
123+
.eth_protocol = eth_protocol,
124+
.seen_long_hdr = (hdr_type == 2) ? 1 : 0,
125+
.seen_short_hdr = (hdr_type == 1) ? 1 : 0,
126+
};
127+
long ret = bpf_map_update_elem(&quic_flows, id, &new_flow, BPF_NOEXIST);
128+
if (ret != 0) {
129+
if (trace_messages && ret != -EEXIST) {
130+
bpf_printk("error adding quic flow %d\n", ret);
131+
}
132+
if (ret == -EEXIST) {
133+
quic_metrics *flow = bpf_map_lookup_elem(&quic_flows, id);
134+
if (flow) {
135+
flow->end_mono_time_ts = now;
136+
flow->packets++;
137+
flow->bytes += len;
138+
}
139+
}
140+
}
141+
}
142+
143+
return 0;
144+
}
145+
146+
#endif /* __QUIC_TRACKER_H__ */

bpf/types.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,19 @@ struct filter_value_t {
299299
// Force emitting enums/structs into the ELF
300300
const static struct filter_value_t *unused12 __attribute__((unused));
301301

302+
// QUIC flow metrics
303+
typedef struct quic_metrics_t {
304+
u64 start_mono_time_ts;
305+
u64 end_mono_time_ts;
306+
u64 bytes;
307+
u32 packets;
308+
u32 version; // QUIC version (from long header), 0 if unknown
309+
u16 eth_protocol; // ETH_P_IP or ETH_P_IPV6
310+
u8 seen_long_hdr; // Saw handshake packets (long header)
311+
u8 seen_short_hdr; // Saw established packets (short header)
312+
} quic_metrics;
313+
314+
// Force emitting struct into the ELF
315+
const static struct quic_metrics_t *unused_quic_metrics __attribute__((unused));
316+
302317
#endif /* __TYPES_H__ */

pkg/agent/agent.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func FlowsAgent(cfg *config.Agent) (*Flows, error) {
176176
UseEbpfManager: cfg.EbpfProgramManagerMode,
177177
BpfManBpfFSPath: cfg.BpfManBpfFSPath,
178178
EnableIPsecTracker: cfg.EnableIPsecTracking,
179+
EnableQUICTracking: cfg.EnableQUICTracking,
179180
FilterConfig: filterRules,
180181
}
181182

pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ type Agent struct {
279279
// This setting is only used when the interface name could not be found for a given index and MAC.
280280
// E.g. "0a:58=eth0" (used for ovn-kubernetes)
281281
PreferredInterfaceForMACPrefix string `env:"PREFERRED_INTERFACE_FOR_MAC_PREFIX"`
282+
// EnableQUICTracking enable QUIC tracking eBPF hook to track QUIC flows
283+
EnableQUICTracking bool `env:"ENABLE_QUIC_TRACKING" envDefault:"false"`
282284

283285
/* Deprecated configs are listed below this line
284286
* See manageDeprecatedConfigs function for details

pkg/decode/decode_protobuf.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ func RecordToMap(fr *model.Record) config.GenericMap {
170170
out["NetworkEvents"] = fr.NetworkMonitorEventsMD
171171
}
172172

173+
if fr.Metrics.QuicMetrics != nil {
174+
out["QuicVersion"] = fr.Metrics.QuicMetrics.Version
175+
out["QuicSeenLongHdr"] = fr.Metrics.QuicMetrics.SeenLongHdr
176+
out["QuicSeenShortHdr"] = fr.Metrics.QuicMetrics.SeenShortHdr
177+
}
178+
173179
return out
174180
}
175181

pkg/decode/decode_protobuf_test.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ func TestPBFlowToMap(t *testing.T) {
100100
},
101101
IpsecEncrypted: 1,
102102
IpsecEncryptedRet: 0,
103+
Quic: &pbflow.Quic{
104+
Version: 1,
105+
SeenLongHdr: 1,
106+
SeenShortHdr: 1,
107+
},
103108
}
104109

105110
out := PBFlowToMap(flow)
@@ -151,13 +156,16 @@ func TestPBFlowToMap(t *testing.T) {
151156
"Direction": "egress",
152157
},
153158
},
154-
"XlatSrcAddr": "1.2.3.4",
155-
"XlatDstAddr": "5.6.7.8",
156-
"XlatSrcPort": uint16(1),
157-
"XlatDstPort": uint16(2),
158-
"ZoneId": uint16(100),
159-
"IPSecRetCode": int32(0),
160-
"IPSecStatus": "success",
159+
"XlatSrcAddr": "1.2.3.4",
160+
"XlatDstAddr": "5.6.7.8",
161+
"XlatSrcPort": uint16(1),
162+
"XlatDstPort": uint16(2),
163+
"ZoneId": uint16(100),
164+
"IPSecRetCode": int32(0),
165+
"IPSecStatus": "success",
166+
"QuicVersion": uint32(1),
167+
"QuicSeenLongHdr": uint8(1),
168+
"QuicSeenShortHdr": uint8(1),
161169
}, out)
162170
}
163171

0 commit comments

Comments
 (0)