Skip to content

Commit 94ec2b6

Browse files
committed
Refresh kafka protocol parsing api versions
Signed-off-by: Dom Del Nano <ddelnano@gmail.com> (cherry picked from commit 628221f)
1 parent da93007 commit 94ec2b6

21 files changed

Lines changed: 487 additions & 272 deletions

File tree

bazel/container_images.bzl

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ def _container_image(name, digest, repository):
4141
repository = image_repo,
4242
)
4343

44+
# Pulls an image directly from its upstream Docker Hub repository, bypassing the
45+
# pixie-io registry mirror. Use this only for images that have not yet been mirrored
46+
# (see scripts/regclient/regbot_deps.yaml). Prefer _container_image once the image is mirrored.
47+
# TODO(ddelnano): Mirror the Kafka test images and switch these back to _container_image.
48+
def _upstream_container_image(name, digest, repository):
49+
container_pull(
50+
name = name,
51+
digest = digest,
52+
registry = "index.docker.io",
53+
repository = repository,
54+
)
55+
4456
def base_images():
4557
# Based on alpine 3.15.9, using OpenResty 1.21.4.1
4658
# https://hub.docker.com/layers/openresty/openresty/alpine-apk-amd64/images/sha256-2259f28de01f85c22e32b6964254a4551c54a1d554cd4b5f1615d7497e1a09ce?context=explore
@@ -221,18 +233,20 @@ def stirling_test_images():
221233
digest = "sha256:55521ffe36911fb4edeaeecb7f9219f9d2a09bc275530212b89e41ab78a7f16d",
222234
)
223235

224-
# Kafka broker image, for testing.
225-
_container_image(
236+
# Kafka broker image (Confluent packaging), for testing.
237+
# Tag: confluentinc/cp-kafka:7.8.0 (Kafka 3.8), linux/amd64. Runs in KRaft mode.
238+
_upstream_container_image(
226239
name = "kafka_base_image",
227240
repository = "confluentinc/cp-kafka",
228-
digest = "sha256:ee6e42ce4f79623c69cf758848de6761c74bf9712697fe68d96291a2b655ce7f",
241+
digest = "sha256:59787f748cee60476bc6d0509bec28e8e5e4225dc96ef9f09866fbde341202e4",
229242
)
230243

231-
# Zookeeper image for Kafka.
232-
_container_image(
233-
name = "zookeeper_base_image",
234-
repository = "confluentinc/cp-zookeeper",
235-
digest = "sha256:87314e87320abf190f0407bf1689f4827661fbb4d671a41cba62673b45b66bfa",
244+
# Kafka broker image (Apache upstream packaging), for testing.
245+
# Tag: apache/kafka:4.0.0 (Kafka 4.0), linux/amd64. Runs in KRaft mode.
246+
_upstream_container_image(
247+
name = "apache_kafka_base_image",
248+
repository = "apache/kafka",
249+
digest = "sha256:01b9a4030e54c6068e66eb3ba4cb82c0d89238629ef1c30d79b86036bf89b1b7",
236250
)
237251

238252
# Tag: node:12.3.1-stretch-slim

src/stirling/protocol_inference/model/ruleset_basic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,15 @@ def infer_mysql_message(buf, count):
247247
# correlation_id => INT32
248248
def infer_kafka_request(buf):
249249
# Note: The Number of Kafka APIs and their versions might change in the future.
250-
kNumAPIs = 62
251-
kMaxAPIVersion = 12
250+
kNumAPIs = 94
251+
kMaxAPIVersion = 18
252252

253253
requestAPIKey = int.from_bytes(buf[4:6], byteorder="big")
254254
if requestAPIKey < 0 or requestAPIKey >= kNumAPIs:
255255
return MessageType.kUnknown
256256

257257
requestAPIVersion = int.from_bytes(buf[6:8], byteorder="big")
258-
if requestAPIVersion < 0 or requestAPIKey > kMaxAPIVersion:
258+
if requestAPIVersion < 0 or requestAPIVersion > kMaxAPIVersion:
259259
return MessageType.kUnknown
260260

261261
correlationID = int.from_bytes(buf[8:12], byteorder="big")

src/stirling/source_connectors/socket_tracer/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,6 @@ pl_cc_bpf_test(
426426
"//src/common/testing/test_utils:cc_library",
427427
"//src/stirling/source_connectors/socket_tracer/testing:cc_library",
428428
"//src/stirling/source_connectors/socket_tracer/testing/container_images:kafka_container",
429-
"//src/stirling/source_connectors/socket_tracer/testing/container_images:zookeeper_container",
430429
],
431430
)
432431

src/stirling/source_connectors/socket_tracer/bcc_bpf/protocol_inference.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,8 @@ static __inline enum message_type_t infer_mysql_message(const char* buf, size_t
391391
// correlation_id => INT32
392392
static __inline enum message_type_t infer_kafka_request(const char* buf) {
393393
// API is Kafka's terminology for opcode.
394-
static const int kNumAPIs = 62;
395-
static const int kMaxAPIVersion = 12;
394+
static const int kNumAPIs = 93;
395+
static const int kMaxAPIVersion = 18;
396396

397397
const int16_t request_API_key = read_big_endian_int16(buf);
398398
if (request_API_key < 0 || request_API_key > kNumAPIs) {

src/stirling/source_connectors/socket_tracer/kafka_trace_bpf_test.cc

Lines changed: 90 additions & 156 deletions
Large diffs are not rendered by default.

src/stirling/source_connectors/socket_tracer/protocols/kafka/common/types.h

Lines changed: 112 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,49 @@ enum class APIKey : int16_t {
9797
kAlterClientQuotas = 49,
9898
kDescribeUserScramCredentials = 50,
9999
kAlterUserScramCredentials = 51,
100+
kVote = 52,
101+
kBeginQuorumEpoch = 53,
102+
kEndQuorumEpoch = 54,
103+
kDescribeQuorum = 55,
104+
// Renamed from AlterIsr to AlterPartition in Kafka 3.x, but the api key is unchanged.
100105
kAlterIsr = 56,
101106
kUpdateFeatures = 57,
107+
kEnvelope = 58,
108+
kFetchSnapshot = 59,
102109
kDescribeCluster = 60,
103110
kDescribeProducers = 61,
111+
kBrokerRegistration = 62,
112+
kBrokerHeartbeat = 63,
113+
kUnregisterBroker = 64,
114+
kDescribeTransactions = 65,
115+
kListTransactions = 66,
116+
kAllocateProducerIds = 67,
117+
kConsumerGroupHeartbeat = 68,
118+
kConsumerGroupDescribe = 69,
119+
kControllerRegistration = 70,
120+
kGetTelemetrySubscriptions = 71,
121+
kPushTelemetry = 72,
122+
kAssignReplicasToDirs = 73,
123+
kListConfigResources = 74,
124+
kDescribeTopicPartitions = 75,
125+
kShareGroupHeartbeat = 76,
126+
kShareGroupDescribe = 77,
127+
kShareFetch = 78,
128+
kShareAcknowledge = 79,
129+
kAddRaftVoter = 80,
130+
kRemoveRaftVoter = 81,
131+
kUpdateRaftVoter = 82,
132+
kInitializeShareGroupState = 83,
133+
kReadShareGroupState = 84,
134+
kWriteShareGroupState = 85,
135+
kDeleteShareGroupState = 86,
136+
kReadShareGroupStateSummary = 87,
137+
kStreamsGroupHeartbeat = 88,
138+
kStreamsGroupDescribe = 89,
139+
kDescribeShareGroupOffsets = 90,
140+
kAlterShareGroupOffsets = 91,
141+
kDeleteShareGroupOffsets = 92,
142+
kStreamsGroupTopologyDescriptionUpdate = 93,
104143
};
105144

106145
// Error Codes
@@ -226,65 +265,106 @@ struct APIVersionData {
226265
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields#KIP482:TheKafkaProtocolshouldSupportOptionalTaggedFields-FlexibleVersions
227266
// Detailed information on each API key:
228267
// https://github.com/apache/kafka/tree/trunk/clients/src/main/resources/common/message
229-
// TODO(chengruizhe): Needs updating for new opcodes.
268+
// Versions below are refreshed against Kafka trunk (4.4.0-SNAPSHOT), which is a superset of all
269+
// released Kafka protocol versions through 4.x. Min versions are intentionally kept low (0, or 1
270+
// for Produce) to make frame boundary detection more robust; only the max and flexible versions
271+
// need to track upstream to avoid rejecting modern clients.
230272
inline const absl::flat_hash_map<APIKey, APIVersionData> APIVersionMap = {
231273
// Setting min supported version to 1 to help finding frame boundary.
232-
{APIKey::kProduce, {1, 9, 9}},
233-
{APIKey::kFetch, {0, 12, 12}},
234-
{APIKey::kListOffsets, {0, 7, 6}},
235-
{APIKey::kMetadata, {0, 12, 9}},
274+
{APIKey::kProduce, {1, 13, 9}},
275+
{APIKey::kFetch, {0, 18, 12}},
276+
{APIKey::kListOffsets, {0, 11, 6}},
277+
{APIKey::kMetadata, {0, 13, 9}},
236278
{APIKey::kLeaderAndIsr, {0, 5, 4}},
237279
{APIKey::kStopReplica, {0, 3, 2}},
238280
{APIKey::kUpdateMetadata, {0, 7, 6}},
239281
{APIKey::kControlledShutdown, {0, 3, 3}},
240-
{APIKey::kOffsetCommit, {0, 8, 8}},
241-
{APIKey::kOffsetFetch, {0, 8, 6}},
242-
{APIKey::kFindCoordinator, {0, 4, 3}},
243-
{APIKey::kJoinGroup, {0, 7, 6}},
282+
{APIKey::kOffsetCommit, {0, 10, 8}},
283+
{APIKey::kOffsetFetch, {0, 10, 6}},
284+
{APIKey::kFindCoordinator, {0, 6, 3}},
285+
{APIKey::kJoinGroup, {0, 9, 6}},
244286
{APIKey::kHeartbeat, {0, 4, 4}},
245-
{APIKey::kLeaveGroup, {0, 4, 4}},
287+
{APIKey::kLeaveGroup, {0, 5, 4}},
246288
{APIKey::kSyncGroup, {0, 5, 4}},
247-
{APIKey::kDescribeGroups, {0, 5, 5}},
248-
{APIKey::kListGroups, {0, 4, 3}},
289+
{APIKey::kDescribeGroups, {0, 6, 5}},
290+
{APIKey::kListGroups, {0, 5, 3}},
249291
{APIKey::kSaslHandshake, {0, 1, -1}},
250-
{APIKey::kApiVersions, {0, 3, 3}},
292+
{APIKey::kApiVersions, {0, 5, 3}},
251293
{APIKey::kCreateTopics, {0, 7, 5}},
252294
{APIKey::kDeleteTopics, {0, 6, 4}},
253295
{APIKey::kDeleteRecords, {0, 2, 2}},
254-
{APIKey::kInitProducerId, {0, 4, 2}},
296+
{APIKey::kInitProducerId, {0, 6, 2}},
255297
{APIKey::kOffsetForLeaderEpoch, {0, 4, 4}},
256-
{APIKey::kAddPartitionsToTxn, {0, 3, 3}},
257-
{APIKey::kAddOffsetsToTxn, {0, 3, 3}},
258-
{APIKey::kEndTxn, {0, 3, 3}},
259-
{APIKey::kWriteTxnMarkers, {0, 1, 1}},
260-
{APIKey::kTxnOffsetCommit, {0, 3, 3}},
261-
{APIKey::kDescribeAcls, {0, 2, 2}},
262-
{APIKey::kCreateAcls, {0, 2, 2}},
263-
{APIKey::kDeleteAcls, {0, 2, 2}},
298+
{APIKey::kAddPartitionsToTxn, {0, 5, 3}},
299+
{APIKey::kAddOffsetsToTxn, {0, 4, 3}},
300+
{APIKey::kEndTxn, {0, 5, 3}},
301+
{APIKey::kWriteTxnMarkers, {0, 2, 1}},
302+
{APIKey::kTxnOffsetCommit, {0, 6, 3}},
303+
{APIKey::kDescribeAcls, {0, 3, 2}},
304+
{APIKey::kCreateAcls, {0, 3, 2}},
305+
{APIKey::kDeleteAcls, {0, 3, 2}},
264306
{APIKey::kDescribeConfigs, {0, 4, 4}},
265307
{APIKey::kAlterConfigs, {0, 2, 2}},
266308
{APIKey::kAlterReplicaLogDirs, {0, 2, 2}},
267-
{APIKey::kDescribeLogDirs, {0, 2, 2}},
309+
{APIKey::kDescribeLogDirs, {0, 5, 2}},
268310
{APIKey::kSaslAuthenticate, {0, 2, 2}},
269311
{APIKey::kCreatePartitions, {0, 3, 2}},
270-
{APIKey::kCreateDelegationToken, {0, 2, 2}},
312+
{APIKey::kCreateDelegationToken, {0, 3, 2}},
271313
{APIKey::kRenewDelegationToken, {0, 2, 2}},
272314
{APIKey::kExpireDelegationToken, {0, 2, 2}},
273-
{APIKey::kDescribeDelegationToken, {0, 2, 2}},
274-
{APIKey::kDeleteGroups, {0, 5, 5}},
315+
{APIKey::kDescribeDelegationToken, {0, 3, 2}},
316+
{APIKey::kDeleteGroups, {0, 3, 2}},
275317
{APIKey::kElectLeaders, {0, 2, 2}},
276318
{APIKey::kIncrementalAlterConfigs, {0, 1, 1}},
277-
{APIKey::kAlterPartitionReassignments, {0, 0, 0}},
319+
{APIKey::kAlterPartitionReassignments, {0, 1, 0}},
278320
{APIKey::kListPartitionReassignments, {0, 0, 0}},
279321
{APIKey::kOffsetDelete, {0, 0, -1}},
280322
{APIKey::kDescribeClientQuotas, {0, 1, 1}},
281323
{APIKey::kAlterClientQuotas, {0, 1, 1}},
282324
{APIKey::kDescribeUserScramCredentials, {0, 0, 0}},
283325
{APIKey::kAlterUserScramCredentials, {0, 0, 0}},
284-
{APIKey::kAlterIsr, {0, 0, 0}},
285-
{APIKey::kUpdateFeatures, {0, 0, 0}},
286-
{APIKey::kDescribeCluster, {0, 0, 0}},
287-
{APIKey::kDescribeProducers, {0, 0, 0}}};
326+
{APIKey::kVote, {0, 2, 0}},
327+
{APIKey::kBeginQuorumEpoch, {0, 1, 1}},
328+
{APIKey::kEndQuorumEpoch, {0, 1, 1}},
329+
{APIKey::kDescribeQuorum, {0, 2, 0}},
330+
{APIKey::kAlterIsr, {0, 3, 0}},
331+
{APIKey::kUpdateFeatures, {0, 2, 0}},
332+
{APIKey::kEnvelope, {0, 0, 0}},
333+
{APIKey::kFetchSnapshot, {0, 1, 0}},
334+
{APIKey::kDescribeCluster, {0, 2, 0}},
335+
{APIKey::kDescribeProducers, {0, 0, 0}},
336+
{APIKey::kBrokerRegistration, {0, 4, 0}},
337+
{APIKey::kBrokerHeartbeat, {0, 2, 0}},
338+
{APIKey::kUnregisterBroker, {0, 0, 0}},
339+
{APIKey::kDescribeTransactions, {0, 0, 0}},
340+
{APIKey::kListTransactions, {0, 2, 0}},
341+
{APIKey::kAllocateProducerIds, {0, 0, 0}},
342+
{APIKey::kConsumerGroupHeartbeat, {0, 1, 0}},
343+
{APIKey::kConsumerGroupDescribe, {0, 1, 0}},
344+
{APIKey::kControllerRegistration, {0, 0, 0}},
345+
{APIKey::kGetTelemetrySubscriptions, {0, 0, 0}},
346+
{APIKey::kPushTelemetry, {0, 0, 0}},
347+
{APIKey::kAssignReplicasToDirs, {0, 0, 0}},
348+
{APIKey::kListConfigResources, {0, 1, 0}},
349+
{APIKey::kDescribeTopicPartitions, {0, 0, 0}},
350+
{APIKey::kShareGroupHeartbeat, {0, 1, 0}},
351+
{APIKey::kShareGroupDescribe, {0, 1, 0}},
352+
{APIKey::kShareFetch, {0, 2, 0}},
353+
{APIKey::kShareAcknowledge, {0, 2, 0}},
354+
{APIKey::kAddRaftVoter, {0, 1, 0}},
355+
{APIKey::kRemoveRaftVoter, {0, 0, 0}},
356+
{APIKey::kUpdateRaftVoter, {0, 0, 0}},
357+
{APIKey::kInitializeShareGroupState, {0, 0, 0}},
358+
{APIKey::kReadShareGroupState, {0, 0, 0}},
359+
{APIKey::kWriteShareGroupState, {0, 1, 0}},
360+
{APIKey::kDeleteShareGroupState, {0, 0, 0}},
361+
{APIKey::kReadShareGroupStateSummary, {0, 1, 0}},
362+
{APIKey::kStreamsGroupHeartbeat, {0, 1, 0}},
363+
{APIKey::kStreamsGroupDescribe, {0, 1, 0}},
364+
{APIKey::kDescribeShareGroupOffsets, {0, 1, 0}},
365+
{APIKey::kAlterShareGroupOffsets, {0, 0, 0}},
366+
{APIKey::kDeleteShareGroupOffsets, {0, 0, 0}},
367+
{APIKey::kStreamsGroupTopologyDescriptionUpdate, {0, 0, 0}}};
288368

289369
inline bool IsFlexible(APIKey api_key, int16_t api_version) {
290370
auto it = APIVersionMap.find(api_key);
@@ -324,7 +404,7 @@ constexpr int kMinReqPacketLength =
324404
kMessageLengthBytes + kAPIKeyLength + kAPIVersionLength + kCorrelationIDLength;
325405
// length, correlation_id
326406
constexpr int kMinRespPacketLength = kMessageLengthBytes + kCorrelationIDLength;
327-
constexpr int kMaxAPIVersion = 12;
407+
constexpr int kMaxAPIVersion = 18;
328408

329409
struct Packet : public FrameBase {
330410
int32_t correlation_id;

src/stirling/source_connectors/socket_tracer/protocols/kafka/decoder/fetch.cc

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ namespace kafka {
2626

2727
StatusOr<FetchReqTopic> PacketDecoder::ExtractFetchReqTopic() {
2828
FetchReqTopic r;
29-
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
29+
// In api_version >= 13, the topic is identified by a UUID instead of a name (KIP-516).
30+
if (api_version_ >= 13) {
31+
PX_ASSIGN_OR_RETURN(r.topic_id, ExtractUUID());
32+
} else {
33+
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
34+
}
3035
PX_ASSIGN_OR_RETURN(r.partitions,
3136
ExtractArray<FetchReqPartition>(&PacketDecoder::ExtractFetchReqPartition));
3237
PX_RETURN_IF_ERROR(/* tag_section */ ExtractTagSection());
@@ -53,15 +58,24 @@ StatusOr<FetchReqPartition> PacketDecoder::ExtractFetchReqPartition() {
5358

5459
StatusOr<FetchForgottenTopicsData> PacketDecoder::ExtractFetchForgottenTopicsData() {
5560
FetchForgottenTopicsData r;
56-
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
61+
// In api_version >= 13, the topic is identified by a UUID instead of a name (KIP-516).
62+
if (api_version_ >= 13) {
63+
PX_ASSIGN_OR_RETURN(r.topic_id, ExtractUUID());
64+
} else {
65+
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
66+
}
5767
PX_ASSIGN_OR_RETURN(r.partition_indices, ExtractArray<int32_t>(&PacketDecoder::ExtractInt32));
5868
PX_RETURN_IF_ERROR(/* tag_section */ ExtractTagSection());
5969
return r;
6070
}
6171

6272
StatusOr<FetchReq> PacketDecoder::ExtractFetchReq() {
6373
FetchReq r;
64-
PX_ASSIGN_OR_RETURN(r.replica_id, ExtractInt32());
74+
// In api_version >= 15, the top-level ReplicaId field was replaced by a tagged ReplicaState
75+
// field (KIP-903), so it is no longer read here; it is skipped as part of the tag section.
76+
if (api_version_ <= 14) {
77+
PX_ASSIGN_OR_RETURN(r.replica_id, ExtractInt32());
78+
}
6579
PX_RETURN_IF_ERROR(/*max_wait_ms*/ ExtractInt32());
6680
PX_RETURN_IF_ERROR(/*min_bytes*/ ExtractInt32());
6781

@@ -127,7 +141,12 @@ StatusOr<FetchRespPartition> PacketDecoder::ExtractFetchRespPartition() {
127141

128142
StatusOr<FetchRespTopic> PacketDecoder::ExtractFetchRespTopic() {
129143
FetchRespTopic r;
130-
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
144+
// In api_version >= 13, the topic is identified by a UUID instead of a name (KIP-516).
145+
if (api_version_ >= 13) {
146+
PX_ASSIGN_OR_RETURN(r.topic_id, ExtractUUID());
147+
} else {
148+
PX_ASSIGN_OR_RETURN(r.name, ExtractString());
149+
}
131150
PX_ASSIGN_OR_RETURN(r.partitions, ExtractArray(&PacketDecoder::ExtractFetchRespPartition));
132151
PX_RETURN_IF_ERROR(/* tag_section */ ExtractTagSection());
133152
return r;

0 commit comments

Comments
 (0)