Browse Source

Fix bounds and unsigned decoding in ETCP packet dumps

master
evgeny 1 day ago
parent
commit
d7fb0c83f9
  1. 20
      src/transport_layer/etcp_debug.c
  2. 74
      tests/test_etcp_session.c

20
src/transport_layer/etcp_debug.c

@ -12,8 +12,9 @@ IP_STR ip_to_string(uint32_t ip) {
return ip_str;
}
// Строит дамп только при включённой категории; длинные цепочки секций безопасно усекаются.
void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int is_send) {
if (!pkt) return;
if (!pkt || !debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) return;
char buf[256];
int pos = 0;
@ -34,8 +35,7 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
int has_payload = 0;
uint16_t offset = 0;
while (offset < len) {
if (offset >= len) break;
while (offset < len && pos < (int)sizeof(buf) - 1) {
uint8_t type = data[offset];
switch (type) {
@ -43,8 +43,8 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
if (offset + 2 > len) { offset = len; break; }
ack_count = data[offset + 1];
if (offset + 8 > len) { offset = len; break; }
ack_last_delivered = data[offset + 2] | (data[offset + 3] << 8) |
(data[offset + 4] << 16) | (data[offset + 5] << 24);
ack_last_delivered = (uint32_t)data[offset + 2] | ((uint32_t)data[offset + 3] << 8) |
((uint32_t)data[offset + 4] << 16) | ((uint32_t)data[offset + 5] << 24);
uint16_t ack_rx_dup = data[offset + 6] | (data[offset + 7] << 8);
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d dup=%u", ack_last_delivered, ack_count, ack_rx_dup);
offset += 8 + ack_count * 8;
@ -61,8 +61,8 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
}
case ETCP_SECTION_PAYLOAD: {
if (offset + 5 > len) { offset = len; break; }
payload_seq = data[offset + 1] | (data[offset + 2] << 8) |
(data[offset + 3] << 16) | (data[offset + 4] << 24);
payload_seq = (uint32_t)data[offset + 1] | ((uint32_t)data[offset + 2] << 8) |
((uint32_t)data[offset + 3] << 16) | ((uint32_t)data[offset + 4] << 24);
payload_size = len - offset - 5;
has_payload = 1;
pos += snprintf(buf + pos, sizeof(buf) - pos, " payload=%u(seq=%u)", payload_size, payload_seq);
@ -75,5 +75,11 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
}
}
if (offset < len || pos >= (int)sizeof(buf)) {
const char marker[] = " [truncated]";
memcpy(buf + sizeof(buf) - sizeof(marker), marker, sizeof(marker));
DEBUG_WARN(DEBUG_CATEGORY_DUMP, "ETCP packet dump truncated: dir=%s data_len=%u offset=%u buffer=%zu",
dir, len, offset, sizeof(buf));
}
DEBUG_DEBUG(DEBUG_CATEGORY_DUMP, "%s", buf);
}

74
tests/test_etcp_session.c

@ -5,6 +5,7 @@
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_connections.h"
#include "etcp_debug.h"
#include "etcp_session.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
@ -18,6 +19,76 @@ static struct UASYNC* ua;
static struct frame frames[4096], saved[4];
static unsigned head, tail, drops, reinit[2], down[2];
static int drop[2][3], blackhole, dead_primary;
static char dump_message[512];
static unsigned dump_count, dump_warnings;
// Перехватывает диагностический вывод без временных файлов и изменения библиотек.
static void capture_dump(int level, const char* category, const char* message) {
if (strcmp(category, "dump")) return;
if (level == DEBUG_LEVEL_WARN) { dump_warnings++; return; }
if (level != DEBUG_LEVEL_DEBUG) return;
snprintf(dump_message, sizeof(dump_message), "%s", message);
dump_count++;
}
// ACK/payload должны корректно декодировать все 32 бита, включая границу знакового int.
static void debug_sequence_boundaries(void) {
const uint32_t values[] = {0, 1, 0x7f000000, 0x7fffffff, 0x80000000, 0xff000000, UINT32_MAX};
struct ETCP_DGRAM* pkt = u_calloc(1, sizeof(*pkt) + ETCP_ACK_BASE_SIZE); CHECK(pkt);
debug_level_t saved = g_debug_config.category_levels[DEBUG_CATEGORY_DUMP];
int console = g_debug_config.console_enabled;
debug_set_category_level(DEBUG_CATEGORY_DUMP, DEBUG_LEVEL_DEBUG);
debug_enable_console(0);
debug_set_log_hook(capture_dump);
for (unsigned i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
for (int ack = 0; ack < 2; ack++) {
memset(pkt->data, 0, ETCP_ACK_BASE_SIZE);
pkt->data[0] = ack ? ETCP_SECTION_ACK : ETCP_SECTION_PAYLOAD;
pkt->data_len = ack ? ETCP_ACK_BASE_SIZE : 5;
for (int j = 0; j < 4; j++) pkt->data[1 + ack + j] = (uint8_t)(values[i] >> (8 * j));
if (ack) pkt->data[6] = pkt->data[7] = 255;
dump_count = 0; dump_message[0] = 0;
etcp_dump_pkt_sections(pkt, NULL, 0);
char expected[80];
if (ack) snprintf(expected, sizeof(expected), " ack=%u/0 dup=65535", values[i]);
else snprintf(expected, sizeof(expected), " payload=0(seq=%u)", values[i]);
CHECK(dump_count == 1 && strstr(dump_message, expected));
}
}
// Укороченная секция не должна читать отсутствующие байты или печатать выдуманный seq.
for (int ack = 0; ack < 2; ack++) {
pkt->data[0] = ack ? ETCP_SECTION_ACK : ETCP_SECTION_PAYLOAD;
for (unsigned len = 0; len < (ack ? ETCP_ACK_BASE_SIZE : 5); len++) {
pkt->data_len = len; dump_count = 0; dump_message[0] = 0;
etcp_dump_pkt_sections(pkt, NULL, 1);
CHECK(dump_count == 1 && !strstr(dump_message, ack ? " ack=" : " payload="));
}
}
// Длинный пакет обязан безопасно усекать дамп, в том числе при выключенной категории.
u_free(pkt);
pkt = u_calloc(1, sizeof(*pkt) + 32 * ETCP_ACK_BASE_SIZE); CHECK(pkt);
for (int ack = 0; ack < 2; ack++) {
unsigned width = ack ? ETCP_ACK_BASE_SIZE : 5;
pkt->data_len = 32 * width;
memset(pkt->data, 0, 32 * ETCP_ACK_BASE_SIZE);
for (unsigned offset = 0; offset < pkt->data_len; offset += width) {
pkt->data[offset] = ack ? ETCP_SECTION_ACK : ETCP_SECTION_TIMESTAMP;
memset(pkt->data + offset + (ack ? 2 : 1), 255, width - (ack ? 2 : 1));
}
for (int enabled = 0; enabled < 2; enabled++) {
debug_set_category_level(DEBUG_CATEGORY_DUMP, enabled ? DEBUG_LEVEL_DEBUG : DEBUG_LEVEL_DISABLED);
dump_count = dump_warnings = 0; dump_message[0] = 0;
etcp_dump_pkt_sections(pkt, NULL, enabled);
if (enabled) CHECK(dump_count == 1 && dump_warnings == 1 && strstr(dump_message, " [truncated]"));
else CHECK(dump_count == 0 && dump_warnings == 0);
}
}
debug_set_log_hook(NULL);
debug_enable_console(console);
debug_set_category_level(DEBUG_CATEGORY_DUMP, saved);
u_free(pkt);
puts("PASS diagnostic uint32 boundaries, truncated sections and long ACK/timestamp dumps");
}
static ssize_t capture(socket_t fd, const void* data, size_t len, const struct sockaddr* addr, socklen_t alen,
struct ETCP_LINK* link, void* arg) {
@ -179,9 +250,10 @@ static void receive_window_case(int wrap) {
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
size_t baseline=u_get_allocated_count();
debug_sequence_boundaries(); CHECK(u_get_allocated_count() == baseline);
const char* log=getenv("SESSION_TEST_LOG");
if (log) { debug_enable_file_output(log,1); debug_set_category_level(DEBUG_CATEGORY_ETCP,DEBUG_LEVEL_DEBUG); }
size_t baseline=u_get_allocated_count();
data_header_validation(); CHECK(u_get_allocated_count() == baseline);
timestamp_boundaries(); CHECK(u_get_allocated_count() == baseline);
receive_window_case(0); CHECK(u_get_allocated_count() == baseline);

Loading…
Cancel
Save