234 lines
12 KiB
C
234 lines
12 KiB
C
/* Калибровка каналов: BWlim смешанных размеров, задержка, ограниченный HOLD, DROP и teardown с таймерами. */
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include "dummynet.h"
|
||
#include "test_clock.h"
|
||
#include "../lib/platform_compat.h"
|
||
#include "../lib/mem.h"
|
||
|
||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL %s:%d: %s", __func__, __LINE__, #x); exit(1); } } while (0)
|
||
static struct UASYNC* ua;
|
||
static struct dummynet* dn;
|
||
static socket_t peers[2];
|
||
static struct sockaddr_in proxy;
|
||
static unsigned received[2];
|
||
static uint64_t bytes[2], last_rx[2];
|
||
static uint8_t seen[2][512];
|
||
static int fail_delay, fail_shaper, fail_packet, fail_delay_arg, fail_send;
|
||
|
||
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
|
||
void* __wrap_uasync_set_timeout(struct UASYNC* loop, int delay, void* arg, timeout_callback_t fn, const char* name) {
|
||
if ((fail_delay && !strcmp(name, "dummynet_delay")) || (fail_shaper && !strcmp(name, "dummynet_shaper"))) return NULL;
|
||
return __real_uasync_set_timeout(loop, delay, arg, fn, name);
|
||
}
|
||
|
||
struct ll_entry* __real_queue_entry_new_from_pool(struct memory_pool*);
|
||
struct ll_entry* __wrap_queue_entry_new_from_pool(struct memory_pool* pool) {
|
||
if (fail_packet) return NULL;
|
||
return __real_queue_entry_new_from_pool(pool);
|
||
}
|
||
|
||
void* __real_u_malloc_impl(uint32_t, const char*);
|
||
void* __wrap_u_malloc_impl(uint32_t size, const char* location) {
|
||
if (fail_delay_arg && strstr(location, "dummynet.c")) return NULL;
|
||
return __real_u_malloc_impl(size, location);
|
||
}
|
||
|
||
ssize_t __real_socket_sendto(socket_t, const void*, size_t, const struct sockaddr*, socklen_t);
|
||
ssize_t __wrap_socket_sendto(socket_t fd, const void* data, size_t size, const struct sockaddr* addr, socklen_t len) {
|
||
if (fail_send && dn && fd == dummynet_get_socket(dn)) { errno = EIO; return -1; }
|
||
return __real_socket_sendto(fd, data, size, addr, len);
|
||
}
|
||
|
||
static void setup(unsigned bw, unsigned delay, unsigned capacity) {
|
||
ua = uasync_create(); CHECK(ua);
|
||
dn = dummynet_create(ua, "127.0.0.1", 0); CHECK(dn);
|
||
memset(received, 0, sizeof(received)); memset(bytes, 0, sizeof(bytes)); memset(seen, 0, sizeof(seen));
|
||
memset(&proxy, 0, sizeof(proxy)); proxy.sin_family = AF_INET;
|
||
proxy.sin_addr.s_addr = htonl(INADDR_LOOPBACK); proxy.sin_port = htons(dummynet_get_listen_port(dn));
|
||
for (int i = 0; i < 2; i++) {
|
||
peers[i] = socket_create_udp(AF_INET); CHECK(peers[i] != SOCKET_INVALID);
|
||
struct sockaddr_in address = proxy; address.sin_port = 0;
|
||
CHECK(bind(peers[i], (struct sockaddr*)&address, sizeof(address)) == 0);
|
||
CHECK(socket_set_nonblocking(peers[i]) == 0);
|
||
int size = 4 * 1024 * 1024;
|
||
CHECK(setsockopt(peers[i], SOL_SOCKET, SO_RCVBUF, (const char*)&size, sizeof(size)) == 0);
|
||
CHECK(dummynet_set_direction(dn, 1-i, delay, 0, bw, capacity, 0,
|
||
"127.0.0.1", socket_get_bound_port(peers[i])) == 0);
|
||
}
|
||
}
|
||
|
||
/* Проверяем каждый байт и уникальность независимо от статистики эмулятора. */
|
||
static void poll_channels(int timeout) {
|
||
test_clock_poll(ua, timeout);
|
||
for (int i = 0; i < 2; i++) {
|
||
uint8_t data[1500]; ssize_t n;
|
||
while ((n = socket_recvfrom(peers[i], data, sizeof(data), NULL, NULL)) >= 0) {
|
||
CHECK(n >= 4);
|
||
unsigned seq = data[0] | (data[1] << 8); CHECK(seq < 512 && !seen[i][seq]);
|
||
seen[i][seq] = 1;
|
||
for (ssize_t p = 2; p < n; p++) CHECK(data[p] == (uint8_t)(seq + p));
|
||
received[i]++; bytes[i] += n; last_rx[i] = get_time_us();
|
||
}
|
||
CHECK(socket_get_error() == ERR_WOULDBLOCK || socket_get_error() == ERR_AGAIN);
|
||
}
|
||
}
|
||
|
||
static void send_packet(int from, unsigned seq, unsigned size) {
|
||
uint8_t data[1500]; CHECK(size >= 4 && size <= sizeof(data));
|
||
data[0] = seq; data[1] = seq >> 8;
|
||
for (unsigned i = 2; i < size; i++) data[i] = seq + i;
|
||
CHECK(socket_sendto(peers[from], data, size, (struct sockaddr*)&proxy, sizeof(proxy)) == (ssize_t)size);
|
||
poll_channels(0);
|
||
}
|
||
|
||
static void run_for(unsigned ms) {
|
||
uint64_t end = get_time_us() + ms * 1000ULL;
|
||
while (get_time_us() < end) poll_channels(10);
|
||
}
|
||
|
||
static void teardown(void) {
|
||
dummynet_destroy(dn); dn = NULL;
|
||
for (int i = 0; i < 2; i++) socket_close_wrapper(peers[i]);
|
||
uasync_poll(ua, 0);
|
||
CHECK(ua->timer_alloc_count == ua->timer_free_count);
|
||
uasync_destroy(ua, 0);
|
||
}
|
||
|
||
static void bandwidth(unsigned bw) {
|
||
setup(bw, 20, 512);
|
||
uint64_t start = get_time_us(), total = 0;
|
||
for (unsigned seq = 0; seq < 256; seq++) {
|
||
unsigned len = seq & 1 ? 1500 : 100;
|
||
total += len;
|
||
send_packet(0, seq, len); send_packet(1, seq, len);
|
||
}
|
||
uint64_t expected = total * 8000 / bw + 20000, end = start + expected + 2000000;
|
||
while ((received[0] < 256 || received[1] < 256) && get_time_us() < end) poll_channels(10);
|
||
for (int i = 0; i < 2; i++) {
|
||
uint64_t elapsed = last_rx[i] - start;
|
||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BW calibration: kbps=%u dir=%d bytes=%llu elapsed_us=%llu expected_us=%llu", bw, i,
|
||
(unsigned long long)bytes[i], (unsigned long long)elapsed, (unsigned long long)expected);
|
||
CHECK(received[i] == 256 && bytes[i] == total);
|
||
CHECK(elapsed * 100 >= expected * 95 && elapsed * 100 <= expected * 105 + 5000);
|
||
const struct dummynet_stats* stats = dummynet_get_stats(dn, 1-i);
|
||
CHECK(stats->sent_bytes == total && !stats->queue_size && !stats->lost && !stats->dropped);
|
||
CHECK(stats->max_queue_delay_us <= elapsed && stats->max_queue_delay_us >= expected - 40000);
|
||
}
|
||
teardown();
|
||
}
|
||
|
||
static void hold_drop(void) {
|
||
setup(80, 20, 8);
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_HOLD) == 0);
|
||
for (unsigned seq = 0; seq < 12; seq++) send_packet(0, seq, 1000);
|
||
run_for(60);
|
||
const struct dummynet_stats* stats = dummynet_get_stats(dn, 0);
|
||
CHECK(!received[1] && stats->queue_size == 8 && stats->dropped == 4);
|
||
dummynet_reset_stats(dn, 0); CHECK(stats->queue_size == 8 && stats->queue_max == 8);
|
||
uint64_t resumed = get_time_us();
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0);
|
||
run_for(50); CHECK(!received[1]);
|
||
run_for(850); CHECK(received[1] == 8 && last_rx[1] - resumed >= 795000);
|
||
// DROP удаляет как delay, так и сериализуемую голову; RUN не возвращает потерянное.
|
||
send_packet(0, 20, 1000); run_for(5);
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_DROP) == 0);
|
||
send_packet(0, 21, 1000); run_for(50);
|
||
CHECK(stats->blocked == 2 && !stats->queue_size && received[1] == 8);
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0);
|
||
send_packet(0, 22, 1000); run_for(25);
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_DROP) == 0);
|
||
CHECK(stats->blocked == 3 && !stats->queue_size);
|
||
CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0);
|
||
send_packet(0, 23, 1000); run_for(150); CHECK(received[1] == 9);
|
||
// Уничтожение одновременно с delay и shaper: после destroycallbacks не должны обращаться к dn.
|
||
send_packet(0, 24, 1000); run_for(25); send_packet(1, 25, 1000);
|
||
teardown();
|
||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS bounded HOLD, paced resume, DROP of pending packets and active teardown");
|
||
}
|
||
|
||
/* Ошибка настройки не меняет действующий канал; повторное изменение BW допустимо с очередью. */
|
||
static void configuration(void) {
|
||
setup(0, 0, 16);
|
||
uint16_t port = socket_get_bound_port(peers[1]);
|
||
CHECK(dummynet_set_direction(dn, 0, 123, 456, 1, 0, 1000, "bad address", port) == -1);
|
||
send_packet(0, 0, 100); send_packet(1, 1, 100); run_for(5);
|
||
CHECK(received[0] == 1 && received[1] == 1);
|
||
CHECK(dummynet_set_direction(dn, 0, UINT32_MAX, UINT32_MAX, 0, 16, 0, "127.0.0.1", port) == -1);
|
||
CHECK(dummynet_set_direction(dn, 0, 10000, 1, 0, 16, 0, "127.0.0.1", port) == -1);
|
||
CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, UINT32_MAX, 0, "127.0.0.1", port) == -1);
|
||
CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "127.0.0.1", socket_get_bound_port(peers[0])) == -1);
|
||
CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "0.0.0.0", port) == -1);
|
||
CHECK(dummynet_set_direction(dn, 0, 20, 0, 80, 16, 0, "127.0.0.1", port) == 0);
|
||
send_packet(0, 2, 1000); run_for(25);
|
||
CHECK(dummynet_set_direction(dn, 0, 0, 0, 8000, 16, 0, "127.0.0.1", port) == 0);
|
||
CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "127.0.0.2", port) == -1);
|
||
run_for(150); CHECK(received[1] == 2 && !dummynet_get_stats(dn, 0)->queue_size);
|
||
teardown();
|
||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS atomic channel configuration, bounds, distinct peers and queued BW changes");
|
||
}
|
||
|
||
/* Нельзя пересылать усечённую UDP-датаграмму. Пустая датаграмма — допустимый пакет. */
|
||
static void packet_bounds(void) {
|
||
setup(0, 0, 16);
|
||
uint8_t large[65507];
|
||
for (size_t i = 0; i < sizeof(large); i++) large[i] = (uint8_t)i;
|
||
for (int i = 0; i < 2; i++) {
|
||
size_t size = i ? sizeof(large) : DUMMYNET_MAX_PKT_SIZE + 1;
|
||
CHECK(socket_sendto(peers[0], large, size, (struct sockaddr*)&proxy, sizeof(proxy)) == (ssize_t)size);
|
||
test_clock_run_for(ua, 50);
|
||
uint8_t reply[1600];
|
||
CHECK(socket_recvfrom(peers[1], reply, sizeof(reply), NULL, NULL) < 0);
|
||
}
|
||
const struct dummynet_stats* stats = dummynet_get_stats(dn, 0);
|
||
CHECK(stats->recv == 2 && stats->dropped == 2 && !stats->sent && !stats->queue_size);
|
||
CHECK(stats->recv_bytes == sizeof(large) + DUMMYNET_MAX_PKT_SIZE + 1);
|
||
CHECK(socket_sendto(peers[0], large, 0, (struct sockaddr*)&proxy, sizeof(proxy)) == 0);
|
||
test_clock_run_for(ua, 50);
|
||
CHECK(socket_recvfrom(peers[1], large, sizeof(large), NULL, NULL) == 0);
|
||
CHECK(stats->recv == 3 && stats->sent == 1 && !stats->sent_bytes);
|
||
teardown();
|
||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS UDP boundaries: oversized packets discarded intact, zero-byte packet delivered");
|
||
}
|
||
|
||
/* Каждый отказ теряет один пакет и оставляет направление пригодным для следующего. */
|
||
static void failures(void) {
|
||
setup(80, 20, 16);
|
||
int* faults[] = {&fail_packet, &fail_delay_arg, &fail_delay, &fail_shaper, &fail_send};
|
||
for (unsigned i = 0; i < sizeof(faults) / sizeof(faults[0]); i++) {
|
||
*faults[i] = 1;
|
||
send_packet(0, i * 2, 1000); run_for(150);
|
||
*faults[i] = 0;
|
||
const struct dummynet_stats* stats = dummynet_get_stats(dn, 0);
|
||
CHECK(stats->dropped == i + 1 && !stats->queue_size && received[1] == i);
|
||
send_packet(0, i * 2 + 1, 1000); run_for(150);
|
||
CHECK(received[1] == i + 1 && !stats->queue_size);
|
||
}
|
||
teardown();
|
||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS packet allocation, delay arg/timer, shaper timer and UDP send failures with recovery");
|
||
}
|
||
|
||
int main(int argc, char** argv) {
|
||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
||
test_clock_init(10);
|
||
CHECK(socket_platform_init() == 0);
|
||
size_t baseline = u_get_allocated_count();
|
||
if (argc == 1) {
|
||
bandwidth(2000); CHECK(u_get_allocated_count() == baseline);
|
||
bandwidth(8000); CHECK(u_get_allocated_count() == baseline);
|
||
hold_drop(); CHECK(u_get_allocated_count() == baseline);
|
||
}
|
||
struct { const char* name; void (*run)(void); } cases[] = {
|
||
{"configuration", configuration}, {"packet_bounds", packet_bounds}, {"failures", failures}
|
||
};
|
||
unsigned selected = 0;
|
||
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||
if (argc > 1 && strcmp(argv[1], cases[i].name)) continue;
|
||
selected++; cases[i].run(); CHECK(u_get_allocated_count() == baseline);
|
||
}
|
||
CHECK(selected && argc <= 2);
|
||
test_clock_finish(); socket_platform_cleanup();
|
||
return 0;
|
||
}
|