From 4f7b896b73abc7a2ccf3bee986a37fb8f4ad5d77 Mon Sep 17 00:00:00 2001 From: "timmy.yuan" Date: Tue, 13 Oct 2026 18:14:14 +0800 Subject: [PATCH] fix(cpworker): keep the packet direction in the gre key when service_tag is unset The GRE key is service_tag | (direction << 28). Without a configured service_tag, cpworker used 0xFFFFFFFF, whose high nibble already fills the direction bits, so every packet carried key 0xFFFFFFFF whatever its direction. cpdaemon produces this config for every CPM GRE strategy without a service tag, so the request/response marking from req_pattern was lost. A configured tag above 28 bits overlapped the direction bits the same way. The default service_tag is now 0x0FFFFFFF, and the key takes only the low 28 bits of the tag: (service_tag & 0x0FFFFFFF) | (direction << 28). Without a service tag, the key is 0x0FFFFFFF / 0x1FFFFFFF / 0x2FFFFFFF instead of 0xFFFFFFFF; receivers reading the tag as key & 0x0FFFFFFF see the same 0xFFFFFFF as before. USAGE-CPWORKER documents the GRE header and key layout. Tests: config_validation expects the 0x0FFFFFFF default; TestWireFormat/gre checks the key without a service_tag in all three directions and with a tag above 28 bits. Both failed before the change. --- cpworker/src/config.c | 8 ++++---- cpworker/src/output_gre.c | 3 ++- cpworker/tests/integration/wire_format_test.go | 13 +++++++++++-- cpworker/tests/unit/config_validation.c | 5 +++-- docs/USAGE-CPWORKER-zh-Hans.md | 12 +++++++++++- docs/USAGE-CPWORKER.md | 12 +++++++++++- 6 files changed, 42 insertions(+), 11 deletions(-) diff --git a/cpworker/src/config.c b/cpworker/src/config.c index 1f408c31..cfdb0cee 100644 --- a/cpworker/src/config.c +++ b/cpworker/src/config.c @@ -728,16 +728,16 @@ static int parse_output_config(cJSON *output_obj, OutputConfig *output, cJSONPar // Service Tag cJSON *service_tag = cJSON_GetObjectItemCaseSensitive(gre_obj, "service_tag"); if (!service_tag) - output->config.gre.service_tag = 0xffffffff; + output->config.gre.service_tag = 0x0fffffff; else { int64_t value; if (!cjson_get_int64_range(service_tag, "gre.service_tag", 0, UINT32_MAX, &value, err)) return PARSE_ERROR; - // The GRE key is service_tag | (direction << 28): the high 4 bits belong to the direction. + // The GRE key is (service_tag & 0x0FFFFFFF) | (direction << 28): the high 4 bits belong to the direction. if (value > 0x0FFFFFFF) - log_warn("gre.service_tag %lld exceeds 28 bits and overlaps the direction bits of the GRE key", - (long long)value); + log_warn("gre.service_tag %lld exceeds 28 bits: the GRE key carries %lld", (long long)value, + (long long)(value & 0x0FFFFFFF)); output->config.gre.service_tag = (uint32_t)value; } diff --git a/cpworker/src/output_gre.c b/cpworker/src/output_gre.c index 1de90a48..9f758d7c 100644 --- a/cpworker/src/output_gre.c +++ b/cpworker/src/output_gre.c @@ -62,7 +62,8 @@ int gre_send_packet(output_base_t *self, const struct pcap_pkthdr *header, const } struct gre_header *gre_hdr = (struct gre_header *)output->buf; - gre_hdr->keybit = htonl(output->service_tag | (direct << 28)); + // The high 4 bits of the key carry the direction, the low 28 bits the service tag. + gre_hdr->keybit = htonl((output->service_tag & 0x0FFFFFFF) | (direct << 28)); memcpy(&(output->buf[GRE_HEADER_LEN]), pkt_data, frame_len); // check error_info diff --git a/cpworker/tests/integration/wire_format_test.go b/cpworker/tests/integration/wire_format_test.go index 4aa48b14..c7c40aa2 100644 --- a/cpworker/tests/integration/wire_format_test.go +++ b/cpworker/tests/integration/wire_format_test.go @@ -262,20 +262,29 @@ func TestWireFormat(t *testing.T) { gre := func(serviceTag int) map[string]any { return map[string]any{"type": "gre", "gre": map[string]any{"host": "127.0.0.1", "service_tag": serviceTag}} } + noTag := map[string]any{"type": "gre", "gre": map[string]any{"host": "127.0.0.1"}} runWireTasks(t, out, []wireTask{ {input: []wirePacket{req, rsp}, output: gre(0x111)}, {input: []wirePacket{req, rsp}, reqPattern: true, output: gre(0x123)}, + {input: []wirePacket{req}, output: noTag}, + {input: []wirePacket{req, rsp}, reqPattern: true, output: noTag}, + {input: []wirePacket{req, rsp}, reqPattern: true, output: gre(0xF0000123)}, }) capturer.Stop() // Flags 0x2000: only the key bit (RFC 2890), version 0. Protocol type 0x6558: - // transparent Ethernet bridging. The key carries the direction in its high 4 bits - // (USAGE-CPWORKER.md, output.gre). The key without a configured service_tag is #296's. + // transparent Ethernet bridging. The key is the direction in its high 4 bits and the + // low 28 bits of service_tag, 0xFFFFFFF when none is configured (USAGE-CPWORKER.md, output.gre). want := [][]byte{ cat(unhex("2000 6558 0000 0111"), req.data), cat(unhex("2000 6558 0000 0111"), rsp.data), cat(unhex("2000 6558 1000 0123"), req.data), cat(unhex("2000 6558 2000 0123"), rsp.data), + cat(unhex("2000 6558 0FFF FFFF"), req.data), + cat(unhex("2000 6558 1FFF FFFF"), req.data), + cat(unhex("2000 6558 2FFF FFFF"), rsp.data), + cat(unhex("2000 6558 1000 0123"), req.data), + cat(unhex("2000 6558 2000 0123"), rsp.data), } assertSameDatagrams(t, ipPayloads(readPcapData(t, capPath), layers.IPProtocolGRE), want) }) diff --git a/cpworker/tests/unit/config_validation.c b/cpworker/tests/unit/config_validation.c index 7f2b449c..311b2b17 100644 --- a/cpworker/tests/unit/config_validation.c +++ b/cpworker/tests/unit/config_validation.c @@ -501,7 +501,7 @@ void test_zmq_service_tag_invalid_rejected(void) assert_rejected(config_with_output(zmq_output_with_service_tag(bad[i])), "invalid zmq.service_tag"); } -// The high 4 bits of the GRE key carry the direction; larger tags are kept (with a warning) as cpdaemon may send them. +// Larger tags are kept (with a warning) as cpdaemon may send them; the GRE output masks them to 28 bits. void test_gre_service_tag_uint32_kept(void) { const char *values[] = {"0", "268435455", "268435456", "4294967295"}; @@ -510,9 +510,10 @@ void test_gre_service_tag_uint32_kept(void) assert_gre_service_tag(gre_output_with_service_tag(values[i]), expected[i]); } +// The default must fit in 28 bits so that the direction in the high 4 bits of the GRE key survives. void test_gre_service_tag_default(void) { - assert_gre_service_tag("{\"type\": \"gre\", \"gre\": {\"host\": \"10.0.0.1\"}}", 0xFFFFFFFF); + assert_gre_service_tag("{\"type\": \"gre\", \"gre\": {\"host\": \"10.0.0.1\"}}", 0x0FFFFFFF); } void test_gre_service_tag_invalid_rejected(void) diff --git a/docs/USAGE-CPWORKER-zh-Hans.md b/docs/USAGE-CPWORKER-zh-Hans.md index 87957700..a5d3aa9d 100644 --- a/docs/USAGE-CPWORKER-zh-Hans.md +++ b/docs/USAGE-CPWORKER-zh-Hans.md @@ -127,7 +127,17 @@ cpworker 是基于 libpcap 的网络抓包工具,支持多种输出方式和 | host | string | - | 目的ip | | bind_device | string | - | 绑定数据包发送接口,默认不指定| | pmtudisc | string | - | 指定MTU发现模式,可选值: do, dont, want | -| service_tag | int | 0xFFFFFFFF | 业务标签,放在 GRE key 中,28 位(0x0-0xFFFFFFF);key 的高 4 位携带方向 | +| service_tag | int | 0xFFFFFFF | 业务标签,放在 GRE key 中,28 位(0x0-0xFFFFFFF);更大的值保留低 28 位。key 的高 4 位携带方向 | + +每个报文是一个 IPv4 包(协议号 47),载荷为 8 字节 GRE 头(RFC 2890)加上采集到的以太网帧: + +| 偏移 | 长度 | 字段 | 值 | +| --- | --- | --- | --- | +| 0 | 2 | flags、version | `0x2000`:带 key,version 0 | +| 2 | 2 | protocol type | `0x6558`:透明以太网桥接 | +| 4 | 4 | key(网络字节序) | 第 28–31 位:方向(0 未配置 `req_pattern`,1 请求,2 响应);第 0–27 位:`service_tag & 0xFFFFFFF` | + +未配置 `service_tag` 时低 28 位为 `0xFFFFFFF`,key 为 `0x0FFFFFFF`、`0x1FFFFFFF` 或 `0x2FFFFFFF`。方向未知的包被丢弃。 ## output.vxlan 参数列表 | 参数 | 类型 | 默认值 | 说明 | diff --git a/docs/USAGE-CPWORKER.md b/docs/USAGE-CPWORKER.md index fb20d5ad..00abe1a9 100644 --- a/docs/USAGE-CPWORKER.md +++ b/docs/USAGE-CPWORKER.md @@ -126,7 +126,17 @@ Values shown in hexadecimal (e.g. `0xFFF`) must be written in decimal in the JSO | host | string | - | Destination IP | | bind_device | string | - | Bind interface (default: any) | | pmtudisc | string | - | MTU discovery mode (do/dont/want) | -| service_tag | int | 0xFFFFFFFF | Service tag carried in the GRE key, 28 bits (0x0-0xFFFFFFF); the high 4 bits of the key carry the direction | +| service_tag | int | 0xFFFFFFF | Service tag carried in the GRE key, 28 bits (0x0-0xFFFFFFF); larger values keep the low 28 bits. The high 4 bits of the key carry the direction | + +Each datagram is an IPv4 packet (protocol 47) whose payload is an 8-byte GRE header (RFC 2890) followed by the captured Ethernet frame: + +| Offset | Size | Field | Value | +| --- | --- | --- | --- | +| 0 | 2 | flags, version | `0x2000`: key present, version 0 | +| 2 | 2 | protocol type | `0x6558`: transparent Ethernet bridging | +| 4 | 4 | key (network byte order) | bits 28-31: direction (0 no `req_pattern`, 1 request, 2 response); bits 0-27: `service_tag & 0xFFFFFFF` | + +Without a `service_tag`, the low 28 bits are `0xFFFFFFF`: the key is `0x0FFFFFFF`, `0x1FFFFFFF` or `0x2FFFFFFF`. Packets of unknown direction are dropped. ## output.vxlan Parameters | Parameter | Type | Default | Description |