5 changed files with 1708 additions and 0 deletions
@ -0,0 +1,570 @@
|
||||
/**
|
||||
* @file dummynet.c |
||||
* @brief Реализация UDP dummynet эмулятора |
||||
*/ |
||||
|
||||
#include "dummynet.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/ll_queue.h" |
||||
#include "../lib/memory_pool.h" |
||||
#include "../lib/socket_compat.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/platform_compat.h" |
||||
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <errno.h> |
||||
|
||||
/* Timebase: 0.1 мс */ |
||||
#define TIMEBASE_US 100 |
||||
#define MS_TO_TB(ms) ((ms) * 10) |
||||
|
||||
/* Burst allowance для shaper (в timebase units) */ |
||||
#define SHAPER_BURST_TB 10 |
||||
|
||||
/* Debug category */ |
||||
#ifndef DEBUG_CATEGORY_DUMMYNET |
||||
#define DEBUG_CATEGORY_DUMMYNET ((debug_category_t)1 << 14) |
||||
#endif |
||||
|
||||
/* Forward declarations */ |
||||
static void dummynet_read_callback(socket_t sock, void* user_arg); |
||||
static void dummynet_delay_callback(void* user_arg); |
||||
static void dummynet_shaper_callback(void* user_arg); |
||||
|
||||
/**
|
||||
* @brief Пакет в обработке (внутренняя структура) |
||||
*/ |
||||
struct dummynet_pkt { |
||||
uint8_t data[DUMMYNET_MAX_PKT_SIZE]; |
||||
size_t len; |
||||
struct sockaddr_storage src_addr; |
||||
socklen_t src_len; |
||||
uint64_t recv_time_tb; |
||||
int direction; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Направление передачи |
||||
*/ |
||||
struct dummynet_dir { |
||||
uint32_t delay_fixed_ms; |
||||
uint32_t delay_random_ms; |
||||
uint32_t bandwidth_kbps; |
||||
uint32_t max_queue_pkts; |
||||
uint32_t loss_permille; |
||||
struct sockaddr_storage dest_addr; |
||||
socklen_t dest_len; |
||||
int configured; |
||||
|
||||
struct ll_queue* queue; |
||||
|
||||
void* shaper_timer; |
||||
int shaper_pending; |
||||
|
||||
/* Отладочные счетчики */ |
||||
uint64_t delay_timer_set; /* Число установок delay таймеров */ |
||||
uint64_t delay_timer_fire; /* Число срабатываний delay таймеров */ |
||||
uint64_t shaper_timer_set; /* Число установок shaper таймеров */ |
||||
uint64_t shaper_timer_fire; /* Число срабатываний shaper таймеров */ |
||||
|
||||
struct dummynet_stats stats; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Контекст dummynet |
||||
*/ |
||||
struct dummynet { |
||||
struct UASYNC* ua; |
||||
socket_t sock; |
||||
struct sockaddr_storage bind_addr; |
||||
socklen_t bind_len; |
||||
uint16_t listen_port; |
||||
|
||||
struct dummynet_dir dirs[DUMMYNET_DIR_COUNT]; |
||||
struct memory_pool* pkt_pool; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Аргумент для delay callback |
||||
*/ |
||||
struct delay_cb_arg { |
||||
struct dummynet* dn; |
||||
struct ll_entry* entry; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Аргумент для shaper callback |
||||
*/ |
||||
struct shaper_cb_arg { |
||||
struct dummynet* dn; |
||||
int dir_idx; |
||||
}; |
||||
|
||||
/* Получить текущее время в timebase units (0.1 мс) */ |
||||
static inline uint64_t get_time_tb(void) { |
||||
struct timespec ts; |
||||
clock_gettime(CLOCK_MONOTONIC, &ts); |
||||
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; |
||||
} |
||||
|
||||
/* Генерация случайного числа 0..max */ |
||||
static inline uint32_t random_range(uint32_t max) { |
||||
if (max == 0) return 0; |
||||
return (uint32_t)rand() % (max + 1); |
||||
} |
||||
|
||||
/* Определяет направление по порту источника */ |
||||
static int dummynet_get_direction(struct dummynet* dn, uint16_t src_port) { |
||||
if (src_port == dn->listen_port - 1) { |
||||
return DUMMYNET_FORWARD; |
||||
} else if (src_port == dn->listen_port + 1) { |
||||
return DUMMYNET_BACKWARD; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
/* Отправляет пакет и планирует следующую отправку */ |
||||
static void dummynet_process_queue(struct dummynet* dn, int dir_idx) { |
||||
struct dummynet_dir* dir = &dn->dirs[dir_idx]; |
||||
|
||||
struct ll_entry* entry = queue_data_get(dir->queue); |
||||
if (!entry) { |
||||
dir->shaper_pending = 0; |
||||
return; |
||||
} |
||||
|
||||
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data; |
||||
|
||||
ssize_t n = socket_sendto(dn->sock, pkt->data, pkt->len, |
||||
(struct sockaddr*)&dir->dest_addr, dir->dest_len); |
||||
if (n < 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "sendto failed: %s", socket_strerror(socket_get_error())); |
||||
} else { |
||||
dir->stats.sent++; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: sent packet (%zd bytes)", dir_idx, n); |
||||
} |
||||
|
||||
size_t bytes_sent = (n > 0) ? (size_t)n : 0; |
||||
dir->stats.queue_size = queue_entry_count(dir->queue); |
||||
queue_entry_free(entry); |
||||
|
||||
/* Планируем следующую отправку */ |
||||
if (dir->stats.queue_size > 0) { |
||||
int delay_tb = 0; |
||||
|
||||
if (dir->bandwidth_kbps > 0) { |
||||
uint64_t delay_calc = (uint64_t)bytes_sent * 80000ULL / (uint64_t)dir->bandwidth_kbps; |
||||
delay_tb = (int)delay_calc; |
||||
if (delay_tb == 0) delay_tb = 1; |
||||
} |
||||
|
||||
struct shaper_cb_arg* sarg = (struct shaper_cb_arg*)malloc(sizeof(struct shaper_cb_arg)); |
||||
if (sarg) { |
||||
sarg->dn = dn; |
||||
sarg->dir_idx = dir_idx; |
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback); |
||||
if (dir->shaper_timer) { |
||||
dir->shaper_timer_set++; |
||||
} else { |
||||
free(sarg); |
||||
dir->shaper_pending = 0; |
||||
} |
||||
} else { |
||||
dir->shaper_pending = 0; |
||||
} |
||||
} else { |
||||
dir->shaper_pending = 0; |
||||
} |
||||
} |
||||
|
||||
/* Callback шейпера */ |
||||
static void dummynet_shaper_callback(void* user_arg) { |
||||
struct shaper_cb_arg* arg = (struct shaper_cb_arg*)user_arg; |
||||
struct dummynet* dn = arg->dn; |
||||
int dir_idx = arg->dir_idx; |
||||
free(arg); |
||||
|
||||
dn->dirs[dir_idx].shaper_timer_fire++; |
||||
dummynet_process_queue(dn, dir_idx); |
||||
} |
||||
|
||||
/* Callback чтения из UDP сокета */ |
||||
static void dummynet_read_callback(socket_t sock, void* user_arg) { |
||||
struct dummynet* dn = (struct dummynet*)user_arg; |
||||
uint8_t buf[DUMMYNET_MAX_PKT_SIZE]; |
||||
struct sockaddr_storage src_addr; |
||||
socklen_t src_len = sizeof(src_addr); |
||||
|
||||
ssize_t n = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src_addr, &src_len); |
||||
if (n < 0) { |
||||
if (socket_get_error() != ERR_WOULDBLOCK && socket_get_error() != ERR_AGAIN) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "recvfrom failed: %s", socket_strerror(socket_get_error())); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
if (n == 0) return; |
||||
|
||||
uint16_t src_port = 0; |
||||
if (src_addr.ss_family == AF_INET) { |
||||
src_port = ntohs(((struct sockaddr_in*)&src_addr)->sin_port); |
||||
} else if (src_addr.ss_family == AF_INET6) { |
||||
src_port = ntohs(((struct sockaddr_in6*)&src_addr)->sin6_port); |
||||
} |
||||
|
||||
int dir_idx = dummynet_get_direction(dn, src_port); |
||||
if (dir_idx < 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Packet from unknown port %u, expected %u or %u", |
||||
src_port, dn->listen_port - 1, dn->listen_port + 1); |
||||
return; |
||||
} |
||||
|
||||
struct dummynet_dir* dir = &dn->dirs[dir_idx]; |
||||
if (!dir->configured) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Direction %d not configured, dropping packet", dir_idx); |
||||
return; |
||||
} |
||||
|
||||
dir->stats.recv++; |
||||
|
||||
/* Random loss check */ |
||||
if (dir->loss_permille > 0) { |
||||
uint32_t roll = (uint32_t)rand() % 1000; |
||||
if (roll < dir->loss_permille) { |
||||
dir->stats.lost++; |
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMMYNET, "Packet lost randomly (roll=%u, threshold=%u)", |
||||
roll, dir->loss_permille); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
/* Выделяем пакет */ |
||||
struct ll_entry* entry = queue_entry_new_from_pool(dn->pkt_pool); |
||||
if (!entry) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to allocate packet entry"); |
||||
return; |
||||
} |
||||
|
||||
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data; |
||||
pkt->len = n; |
||||
memcpy(pkt->data, buf, n); |
||||
pkt->src_addr = src_addr; |
||||
pkt->src_len = src_len; |
||||
pkt->recv_time_tb = get_time_tb(); |
||||
pkt->direction = dir_idx; |
||||
|
||||
/* Вычисляем задержку */ |
||||
uint32_t delay_ms = dir->delay_fixed_ms + random_range(dir->delay_random_ms); |
||||
if (delay_ms > DUMMYNET_MAX_DELAY_MS) { |
||||
delay_ms = DUMMYNET_MAX_DELAY_MS; |
||||
} |
||||
int delay_tb = MS_TO_TB(delay_ms); |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet recv (%zd bytes), delay %u ms", |
||||
dir_idx, n, delay_ms); |
||||
|
||||
/* Создаём таймер задержки */ |
||||
struct delay_cb_arg* targ = (struct delay_cb_arg*)malloc(sizeof(struct delay_cb_arg)); |
||||
if (!targ) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to allocate timer arg"); |
||||
dir->stats.dropped++; |
||||
queue_entry_free(entry); |
||||
return; |
||||
} |
||||
targ->dn = dn; |
||||
targ->entry = entry; |
||||
|
||||
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback); |
||||
if (!timer) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to set delay timer"); |
||||
dir->stats.dropped++; |
||||
free(targ); |
||||
queue_entry_free(entry); |
||||
return; |
||||
} |
||||
dir->delay_timer_set++; |
||||
} |
||||
|
||||
/* Callback таймера задержки */ |
||||
static void dummynet_delay_callback(void* user_arg) { |
||||
struct delay_cb_arg* arg = (struct delay_cb_arg*)user_arg; |
||||
struct dummynet* dn = arg->dn; |
||||
struct ll_entry* entry = arg->entry; |
||||
free(arg); |
||||
|
||||
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data; |
||||
int dir_idx = pkt->direction; |
||||
struct dummynet_dir* dir = &dn->dirs[dir_idx]; |
||||
dir->delay_timer_fire++; |
||||
|
||||
/* Проверяем размер очереди */ |
||||
int queue_count = queue_entry_count(dir->queue); |
||||
if ((uint32_t)queue_count >= dir->max_queue_pkts) { |
||||
dir->stats.dropped++; |
||||
// DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue full (%d packets), dropping packet", dir_idx, queue_count);
|
||||
queue_entry_free(entry); |
||||
return; |
||||
} |
||||
|
||||
/* Добавляем в очередь */ |
||||
uint32_t id = (uint32_t)(uintptr_t)entry; |
||||
if (queue_data_put(dir->queue, entry, id) != 0) { |
||||
dir->stats.dropped++; |
||||
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx); |
||||
queue_entry_free(entry); |
||||
return; |
||||
} |
||||
|
||||
dir->stats.queue_size = queue_count + 1; |
||||
if ((uint32_t)(queue_count + 1) > dir->stats.queue_max) { |
||||
dir->stats.queue_max = queue_count + 1; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)", |
||||
dir_idx, queue_count + 1); |
||||
|
||||
/* Запускаем шейпер если не активен */ |
||||
if (!dir->shaper_pending) { |
||||
dir->shaper_pending = 1; |
||||
struct shaper_cb_arg* sarg = (struct shaper_cb_arg*)malloc(sizeof(struct shaper_cb_arg)); |
||||
if (sarg) { |
||||
sarg->dn = dn; |
||||
sarg->dir_idx = dir_idx; |
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, 0, sarg, dummynet_shaper_callback); |
||||
} else { |
||||
dir->shaper_pending = 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Создаёт dummynet контекст */ |
||||
struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_t listen_port) { |
||||
if (!ua || listen_port == 0 || listen_port < 2 || listen_port > 65533) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Invalid parameters"); |
||||
return NULL; |
||||
} |
||||
|
||||
struct dummynet* dn = (struct dummynet*)calloc(1, sizeof(struct dummynet)); |
||||
if (!dn) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to allocate dummynet context"); |
||||
return NULL; |
||||
} |
||||
|
||||
dn->ua = ua; |
||||
dn->listen_port = listen_port; |
||||
|
||||
/* Создаём пул для пакетов */ |
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt)); |
||||
if (!dn->pkt_pool) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool"); |
||||
free(dn); |
||||
return NULL; |
||||
} |
||||
|
||||
/* Создаём очереди для направлений */ |
||||
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) { |
||||
dn->dirs[i].queue = queue_new(ua, 0); |
||||
if (!dn->dirs[i].queue) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create queue for direction %d", i); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
/* Создаём UDP сокет */ |
||||
dn->sock = socket_create_udp(AF_INET); |
||||
if (dn->sock == SOCKET_INVALID) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create UDP socket"); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
|
||||
if (socket_set_nonblocking(dn->sock) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to set nonblocking"); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
|
||||
/* Привязываем сокет */ |
||||
struct sockaddr_in bind_addr; |
||||
memset(&bind_addr, 0, sizeof(bind_addr)); |
||||
bind_addr.sin_family = AF_INET; |
||||
bind_addr.sin_port = htons(listen_port); |
||||
|
||||
if (bind_ip && strcmp(bind_ip, "0.0.0.0") != 0) { |
||||
if (inet_pton(AF_INET, bind_ip, &bind_addr.sin_addr) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Invalid bind IP: %s", bind_ip); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
} else { |
||||
bind_addr.sin_addr.s_addr = INADDR_ANY; |
||||
} |
||||
|
||||
if (bind(dn->sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr)) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to bind to %s:%u: %s", |
||||
bind_ip ? bind_ip : "0.0.0.0", listen_port, socket_strerror(socket_get_error())); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
|
||||
/* Добавляем сокет в uasync */ |
||||
void* sock_id = uasync_add_socket_t(ua, dn->sock, dummynet_read_callback, NULL, NULL, dn); |
||||
if (!sock_id) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to add socket to uasync"); |
||||
dummynet_destroy(dn); |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMMYNET, "Dummynet created on %s:%u (forward: %u->%u, backward: %u->%u)", |
||||
bind_ip ? bind_ip : "0.0.0.0", listen_port, |
||||
listen_port - 1, listen_port + 1, |
||||
listen_port + 1, listen_port - 1); |
||||
|
||||
return dn; |
||||
} |
||||
|
||||
/* Уничтожает dummynet контекст */ |
||||
void dummynet_destroy(struct dummynet* dn) { |
||||
if (!dn) return; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMMYNET, "Destroying dummynet"); |
||||
|
||||
if (dn->sock != SOCKET_INVALID) { |
||||
uasync_remove_socket_t(dn->ua, dn->sock); |
||||
socket_close_wrapper(dn->sock); |
||||
} |
||||
|
||||
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) { |
||||
if (dn->dirs[i].shaper_timer) { |
||||
uasync_cancel_timeout(dn->ua, dn->dirs[i].shaper_timer); |
||||
} |
||||
if (dn->dirs[i].queue) { |
||||
struct ll_entry* entry; |
||||
while ((entry = queue_data_get(dn->dirs[i].queue)) != NULL) { |
||||
queue_entry_free(entry); |
||||
} |
||||
queue_free(dn->dirs[i].queue); |
||||
} |
||||
} |
||||
|
||||
if (dn->pkt_pool) { |
||||
memory_pool_destroy(dn->pkt_pool); |
||||
} |
||||
|
||||
free(dn); |
||||
} |
||||
|
||||
/* Настраивает параметры направления */ |
||||
int dummynet_set_direction(struct dummynet* dn, int direction, |
||||
uint32_t delay_fixed_ms, uint32_t delay_random_ms, |
||||
uint32_t bandwidth_kbps, uint32_t max_queue_pkts, |
||||
uint32_t loss_permille, |
||||
const char* dest_ip, uint16_t dest_port) { |
||||
if (!dn || direction < 0 || direction >= DUMMYNET_DIR_COUNT) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!dest_ip || dest_port == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Invalid destination"); |
||||
return -1; |
||||
} |
||||
|
||||
if (loss_permille > DUMMYNET_MAX_LOSS_PERMILLE) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Loss permille %u clamped to %d", |
||||
loss_permille, DUMMYNET_MAX_LOSS_PERMILLE); |
||||
loss_permille = DUMMYNET_MAX_LOSS_PERMILLE; |
||||
} |
||||
|
||||
struct dummynet_dir* dir = &dn->dirs[direction]; |
||||
|
||||
dir->delay_fixed_ms = delay_fixed_ms; |
||||
dir->delay_random_ms = delay_random_ms; |
||||
dir->bandwidth_kbps = bandwidth_kbps; |
||||
dir->max_queue_pkts = max_queue_pkts; |
||||
dir->loss_permille = loss_permille; |
||||
|
||||
memset(&dir->dest_addr, 0, sizeof(dir->dest_addr)); |
||||
struct sockaddr_in* dest_sin = (struct sockaddr_in*)&dir->dest_addr; |
||||
dest_sin->sin_family = AF_INET; |
||||
dest_sin->sin_port = htons(dest_port); |
||||
|
||||
if (inet_pton(AF_INET, dest_ip, &dest_sin->sin_addr) != 1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Invalid destination IP: %s", dest_ip); |
||||
return -1; |
||||
} |
||||
dir->dest_len = sizeof(struct sockaddr_in); |
||||
dir->configured = 1; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMMYNET, "Direction %d configured: delay=%u+%u ms, bw=%u kbps, " |
||||
"queue=%u pkts, loss=%u.%u%%, dest=%s:%u", |
||||
direction, delay_fixed_ms, delay_random_ms, bandwidth_kbps, |
||||
max_queue_pkts, loss_permille / 10, loss_permille % 10, |
||||
dest_ip, dest_port); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* Получает статистику направления */ |
||||
const struct dummynet_stats* dummynet_get_stats(struct dummynet* dn, int direction) { |
||||
if (!dn || direction < 0 || direction >= DUMMYNET_DIR_COUNT) { |
||||
return NULL; |
||||
} |
||||
return &dn->dirs[direction].stats; |
||||
} |
||||
|
||||
/* Сбрасывает статистику */ |
||||
void dummynet_reset_stats(struct dummynet* dn, int direction) { |
||||
if (!dn) return; |
||||
|
||||
if (direction < 0) { |
||||
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) { |
||||
memset(&dn->dirs[i].stats, 0, sizeof(struct dummynet_stats)); |
||||
} |
||||
} else if (direction >= 0 && direction < DUMMYNET_DIR_COUNT) { |
||||
memset(&dn->dirs[direction].stats, 0, sizeof(struct dummynet_stats)); |
||||
} |
||||
} |
||||
|
||||
/* Возвращает файловый дескриптор сокета */ |
||||
int dummynet_get_socket(struct dummynet* dn) { |
||||
if (!dn) return SOCKET_INVALID; |
||||
return dn->sock; |
||||
} |
||||
|
||||
/* Возвращает listen_port */ |
||||
uint16_t dummynet_get_listen_port(struct dummynet *dn) { |
||||
if (!dn) return 0; |
||||
return dn->listen_port; |
||||
} |
||||
|
||||
/* Возвращает указатель на UASYNC */ |
||||
struct UASYNC *dummynet_get_uasync(struct dummynet *dn) { |
||||
if (!dn) return NULL; |
||||
return dn->ua; |
||||
} |
||||
|
||||
/* Возвращает размер очереди направления */ |
||||
int dummynet_get_queue_size(struct dummynet *dn, int direction) { |
||||
if (!dn || direction < 0 || direction >= DUMMYNET_DIR_COUNT) { |
||||
return -1; |
||||
} |
||||
return queue_entry_count(dn->dirs[direction].queue); |
||||
} |
||||
|
||||
/* Получает отладочные счетчики направления */ |
||||
void dummynet_get_debug_counters(struct dummynet *dn, int direction, |
||||
uint64_t *delay_set, uint64_t *delay_fire, |
||||
uint64_t *shaper_set, uint64_t *shaper_fire) { |
||||
if (!dn || direction < 0 || direction >= DUMMYNET_DIR_COUNT) { |
||||
*delay_set = *delay_fire = *shaper_set = *shaper_fire = 0; |
||||
return; |
||||
} |
||||
struct dummynet_dir *dir = &dn->dirs[direction]; |
||||
*delay_set = dir->delay_timer_set; |
||||
*delay_fire = dir->delay_timer_fire; |
||||
*shaper_set = dir->shaper_timer_set; |
||||
*shaper_fire = dir->shaper_timer_fire; |
||||
} |
||||
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file dummynet.h |
||||
* @brief UDP dummynet эмулятор с задержкой, bandwidth limiting и случайными потерями |
||||
* |
||||
* Эмулирует сетевые условия: задержку (fixed + random), ограничение пропускной способности |
||||
* и случайные потери пакетов. Работает как "человек посередине" - принимает пакеты на |
||||
* listen_port и пересылает между port-1 и port+1 с эмуляцией условий канала. |
||||
* |
||||
* Архитектура: |
||||
* [UDP recv] -> [delay timer] -> [check queue/drop] -> [ll_queue] -> [shaper timer] -> [UDP send] |
||||
* ^ |
||||
* loss check |
||||
* |
||||
* Поток данных: |
||||
* - Слушаем на bind_ip:listen_port |
||||
* - Пакеты от listen_port-1 -> отправляем на dest_forward (forward direction) |
||||
* - Пакеты от listen_port+1 -> отправляем на dest_backward (backward direction) |
||||
* - Каждое направление имеет свою очередь и параметры эмуляции |
||||
*/ |
||||
|
||||
#ifndef DUMMYNET_H |
||||
#define DUMMYNET_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
|
||||
// Forward declarations
|
||||
struct UASYNC; |
||||
struct ll_queue; |
||||
struct ll_entry; |
||||
|
||||
/* Направления передачи */ |
||||
#define DUMMYNET_FORWARD 0 // port-1 -> port+1
|
||||
#define DUMMYNET_BACKWARD 1 // port+1 -> port-1
|
||||
#define DUMMYNET_DIR_COUNT 2 |
||||
|
||||
/* Максимальный размер UDP пакета */ |
||||
#define DUMMYNET_MAX_PKT_SIZE 1500 |
||||
|
||||
/* Максимальное время задержки (10 секунд) */ |
||||
#define DUMMYNET_MAX_DELAY_MS 10000 |
||||
|
||||
/* Максимальное значение loss_permille (1000 = 100%) */ |
||||
#define DUMMYNET_MAX_LOSS_PERMILLE 1000 |
||||
|
||||
/**
|
||||
* @brief Статистика направления |
||||
*/ |
||||
struct dummynet_stats { |
||||
uint64_t recv; // Принято пакетов
|
||||
uint64_t sent; // Отправлено пакетов
|
||||
uint64_t dropped; // Дропнуто (queue full)
|
||||
uint64_t lost; // Потеряно случайно (random loss)
|
||||
uint32_t queue_size; // Текущий размер очереди
|
||||
uint32_t queue_max; // Максимальный размер очереди (пиковый)
|
||||
}; |
||||
|
||||
/**
|
||||
* @brief Контекст dummynet |
||||
*/ |
||||
struct dummynet; |
||||
|
||||
/**
|
||||
* @brief Создаёт dummynet контекст |
||||
* |
||||
* @param ua Экземпляр uasync (обязателен) |
||||
* @param bind_ip IP адрес для привязки (NULL или "0.0.0.0" для всех интерфейсов) |
||||
* @param listen_port Порт для прослушивания (средний порт, port-1 и port+1 - клиенты) |
||||
* @return Указатель на контекст или NULL при ошибке |
||||
*/ |
||||
struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_t listen_port); |
||||
|
||||
/**
|
||||
* @brief Уничтожает dummynet контекст |
||||
* |
||||
* Освобождает все ресурсы, закрывает сокет, отменяет таймеры. |
||||
* |
||||
* @param dn Контекст dummynet |
||||
*/ |
||||
void dummynet_destroy(struct dummynet* dn); |
||||
|
||||
/**
|
||||
* @brief Настраивает параметры направления |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @param direction Направление: DUMMYNET_FORWARD или DUMMYNET_BACKWARD |
||||
* @param delay_fixed_ms Фиксированная задержка (мс) |
||||
* @param delay_random_ms Случайная составляющая задержки (0..random_ms, мс) |
||||
* @param bandwidth_kbps Пропускная способность (кбит/с, 0 = unlimited) |
||||
* @param max_queue_pkts Максимальный размер очереди (пакетов) |
||||
* @param loss_permille Вероятность потери пакета (0.1%, 0-1000) |
||||
* @param dest_ip IP адрес назначения |
||||
* @param dest_port Порт назначения |
||||
* @return 0 при успехе, -1 при ошибке |
||||
*/ |
||||
int dummynet_set_direction(struct dummynet* dn, int direction, |
||||
uint32_t delay_fixed_ms, uint32_t delay_random_ms, |
||||
uint32_t bandwidth_kbps, uint32_t max_queue_pkts, |
||||
uint32_t loss_permille, |
||||
const char* dest_ip, uint16_t dest_port); |
||||
|
||||
/**
|
||||
* @brief Получает статистику направления |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @param direction Направление |
||||
* @return Указатель на статистику или NULL |
||||
*/ |
||||
const struct dummynet_stats* dummynet_get_stats(struct dummynet* dn, int direction); |
||||
|
||||
/**
|
||||
* @brief Сбрасывает статистику направления |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @param direction Направление (-1 для всех направлений) |
||||
*/ |
||||
void dummynet_reset_stats(struct dummynet* dn, int direction); |
||||
|
||||
/**
|
||||
* @brief Возвращает файловый дескриптор сокета (для отладки) |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @return socket_t или SOCKET_INVALID |
||||
*/ |
||||
int dummynet_get_socket(struct dummynet* dn); |
||||
|
||||
/**
|
||||
* @brief Возвращает listen_port |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @return Порт или 0 |
||||
*/ |
||||
uint16_t dummynet_get_listen_port(struct dummynet *dn); |
||||
|
||||
/**
|
||||
* @brief Возвращает указатель на UASYNC (для запуска poll) |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @return Указатель на UASYNC или NULL |
||||
*/ |
||||
struct UASYNC *dummynet_get_uasync(struct dummynet *dn); |
||||
|
||||
/**
|
||||
* @brief Возвращает размер очереди направления |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @param direction Направление |
||||
* @return Размер очереди или -1 |
||||
*/ |
||||
int dummynet_get_queue_size(struct dummynet *dn, int direction); |
||||
|
||||
/**
|
||||
* @brief Получает отладочные счетчики направления |
||||
* |
||||
* @param dn Контекст dummynet |
||||
* @param direction Направление |
||||
* @param delay_set Выход: число установок delay таймеров |
||||
* @param delay_fire Выход: число срабатываний delay таймеров |
||||
* @param shaper_set Выход: число установок shaper таймеров |
||||
* @param shaper_fire Выход: число срабатываний shaper таймеров |
||||
*/ |
||||
void dummynet_get_debug_counters(struct dummynet *dn, int direction, |
||||
uint64_t *delay_set, uint64_t *delay_fire, |
||||
uint64_t *shaper_set, uint64_t *shaper_fire); |
||||
|
||||
#endif // DUMMYNET_H
|
||||
@ -0,0 +1,485 @@
|
||||
/**
|
||||
* @file test_dummynet.c |
||||
* @brief Комплексный тест модуля dummynet (строго однопоточный, только uasync) |
||||
* |
||||
* Архитектура: |
||||
* - Все операции через uasync (таймеры и сокеты) |
||||
* - Без usleep/delay - только uasync_poll() |
||||
* - Все callback в одном контексте |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdint.h> |
||||
#include <inttypes.h> |
||||
#include <sys/time.h> |
||||
|
||||
#include "../lib/u_async.h" |
||||
#include "../lib/socket_compat.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../src/dummynet.h" |
||||
|
||||
#define LISTEN_PORT 18000 |
||||
#define PKT_COUNT 1000 |
||||
#define MAX_PKT_SIZE 1500 |
||||
#define MIN_PKT_SIZE 500 |
||||
#define SEND_INTERVAL_US 100 /* 0.1ms между пакетами = 10 pkt/ms */ |
||||
|
||||
/* Формат пакета */ |
||||
struct test_pkt { |
||||
uint32_t seq_num; |
||||
uint64_t send_time_us; |
||||
uint16_t data_len; |
||||
} __attribute__((packed)); |
||||
|
||||
/* Состояние теста */ |
||||
struct test_state { |
||||
struct UASYNC* ua; |
||||
struct dummynet* dn; |
||||
socket_t sock_a; /* Порт listen_port-1 */ |
||||
socket_t sock_b; /* Порт listen_port+1 */ |
||||
|
||||
/* Отправка */ |
||||
uint32_t next_seq; |
||||
uint32_t pkts_to_send; |
||||
void* send_timer; |
||||
|
||||
/* Статистика приема */ |
||||
uint32_t recv_a; /* Получено на A (от B) */ |
||||
uint32_t recv_b; /* Получено на B (от A) */ |
||||
uint32_t lost_a; |
||||
uint32_t lost_b; |
||||
uint64_t latency_sum_a; |
||||
uint64_t latency_sum_b; |
||||
uint32_t latency_count_a; |
||||
uint32_t latency_count_b; |
||||
|
||||
/* Флаги завершения */ |
||||
int done; |
||||
int passed; |
||||
}; |
||||
|
||||
static inline uint64_t get_time_us(void) { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; |
||||
} |
||||
|
||||
/* Функция отправки одного пакета */ |
||||
static void send_one_packet(struct test_state* st, uint32_t seq) { |
||||
uint8_t buf[MAX_PKT_SIZE]; |
||||
struct test_pkt* pkt = (struct test_pkt*)buf; |
||||
/* Максимальный размер данных: MAX_PKT_SIZE - sizeof(*pkt) */ |
||||
uint16_t max_data_len = MAX_PKT_SIZE - sizeof(struct test_pkt); |
||||
uint16_t data_len = MIN_PKT_SIZE + (rand() % (max_data_len - MIN_PKT_SIZE + 1)); |
||||
|
||||
pkt->seq_num = seq; |
||||
pkt->send_time_us = get_time_us(); |
||||
pkt->data_len = data_len; |
||||
|
||||
/* Заполняем данные */ |
||||
for (int i = 0; i < data_len; i++) { |
||||
buf[sizeof(*pkt) + i] = (uint8_t)((seq + i) & 0xFF); |
||||
} |
||||
|
||||
/* A -> dummynet (forward) */ |
||||
struct sockaddr_in dest; |
||||
memset(&dest, 0, sizeof(dest)); |
||||
dest.sin_family = AF_INET; |
||||
dest.sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
dest.sin_port = htons(LISTEN_PORT); |
||||
|
||||
socket_sendto(st->sock_a, buf, sizeof(*pkt) + data_len,
|
||||
(struct sockaddr*)&dest, sizeof(dest)); |
||||
|
||||
/* B -> dummynet (backward) */ |
||||
pkt->seq_num = seq + 10000; /* Отличаем пакеты от B */ |
||||
pkt->send_time_us = get_time_us(); |
||||
socket_sendto(st->sock_b, buf, sizeof(*pkt) + data_len, |
||||
(struct sockaddr*)&dest, sizeof(dest)); |
||||
} |
||||
|
||||
/* Callback отправки пакета (для интервальной отправки) */ |
||||
static void send_timer_callback(void* arg) { |
||||
struct test_state* st = (struct test_state*)arg; |
||||
|
||||
if (st->next_seq >= st->pkts_to_send) { |
||||
/* Все отправили, больше не запускаем таймер */ |
||||
st->send_timer = NULL; |
||||
return; |
||||
} |
||||
|
||||
send_one_packet(st, st->next_seq); |
||||
st->next_seq++; |
||||
|
||||
/* Перезапускаем таймер на следующую отправку (10 timebase = 1ms) */ |
||||
st->send_timer = uasync_set_timeout(st->ua, 10, st, send_timer_callback); |
||||
} |
||||
|
||||
/* Burst отправка - все пакеты сразу */ |
||||
static void send_burst(struct test_state* st) { |
||||
for (uint32_t i = 0; i < st->pkts_to_send; i++) { |
||||
send_one_packet(st, i); |
||||
st->next_seq++; |
||||
} |
||||
} |
||||
|
||||
/* Callback приема на сокете A */ |
||||
static void recv_callback_a(socket_t sock, void* arg) { |
||||
struct test_state* st = (struct test_state*)arg; |
||||
uint8_t buf[MAX_PKT_SIZE]; |
||||
struct sockaddr_storage src; |
||||
socklen_t src_len = sizeof(src); |
||||
|
||||
ssize_t n = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src, &src_len); |
||||
if (n < (ssize_t)sizeof(struct test_pkt)) return; |
||||
|
||||
struct test_pkt* pkt = (struct test_pkt*)buf; |
||||
uint64_t now = get_time_us(); |
||||
uint64_t latency = now - pkt->send_time_us; |
||||
|
||||
st->recv_a++; |
||||
st->latency_sum_a += latency; |
||||
st->latency_count_a++; |
||||
} |
||||
|
||||
/* Callback приема на сокете B */ |
||||
static void recv_callback_b(socket_t sock, void* arg) { |
||||
struct test_state* st = (struct test_state*)arg; |
||||
uint8_t buf[MAX_PKT_SIZE]; |
||||
struct sockaddr_storage src; |
||||
socklen_t src_len = sizeof(src); |
||||
|
||||
ssize_t n = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src, &src_len); |
||||
if (n < (ssize_t)sizeof(struct test_pkt)) return; |
||||
|
||||
struct test_pkt* pkt = (struct test_pkt*)buf; |
||||
uint64_t now = get_time_us(); |
||||
uint64_t latency = now - pkt->send_time_us; |
||||
|
||||
st->recv_b++; |
||||
st->latency_sum_b += latency; |
||||
st->latency_count_b++; |
||||
} |
||||
|
||||
/* Создание привязанного сокета */ |
||||
static socket_t create_bound_socket(uint16_t port) { |
||||
socket_t sock = socket_create_udp(AF_INET); |
||||
if (sock == SOCKET_INVALID) return SOCKET_INVALID; |
||||
|
||||
int reuse = 1; |
||||
int rcvbuf = 2 * 1024 * 1024; /* 2MB receive buffer */ |
||||
int sndbuf = 2 * 1024 * 1024; /* 2MB send buffer */ |
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); |
||||
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); |
||||
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); |
||||
|
||||
struct sockaddr_in addr; |
||||
memset(&addr, 0, sizeof(addr)); |
||||
addr.sin_family = AF_INET; |
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
addr.sin_port = htons(port); |
||||
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) { |
||||
socket_close_wrapper(sock); |
||||
return SOCKET_INVALID; |
||||
} |
||||
|
||||
return sock; |
||||
} |
||||
|
||||
/* Запуск одного сценария */ |
||||
static int run_scenario(const char* name,
|
||||
uint32_t delay_fixed_ms,
|
||||
uint32_t delay_random_ms, |
||||
uint32_t bandwidth_kbps, |
||||
uint32_t max_queue_pkts, |
||||
uint32_t loss_permille) { |
||||
|
||||
printf("\n=== Scenario: %s ===\n", name); |
||||
printf("Config: delay=%u+%u ms, bw=%u kbps, queue=%u, loss=%u.%u%%\n", |
||||
delay_fixed_ms, delay_random_ms, bandwidth_kbps, max_queue_pkts, |
||||
loss_permille / 10, loss_permille % 10); |
||||
|
||||
struct test_state st; |
||||
memset(&st, 0, sizeof(st)); |
||||
st.pkts_to_send = PKT_COUNT; |
||||
|
||||
/* Создаем uasync */ |
||||
st.ua = uasync_create(); |
||||
if (!st.ua) { |
||||
printf("[FAIL] uasync_create failed\n"); |
||||
return 0; |
||||
} |
||||
|
||||
/* Создаем сокеты */ |
||||
st.sock_a = create_bound_socket(LISTEN_PORT - 1); |
||||
st.sock_b = create_bound_socket(LISTEN_PORT + 1); |
||||
|
||||
if (st.sock_a == SOCKET_INVALID || st.sock_b == SOCKET_INVALID) { |
||||
printf("[FAIL] Failed to create sockets\n"); |
||||
uasync_destroy(st.ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
/* Добавляем сокеты в uasync */ |
||||
uasync_add_socket_t(st.ua, st.sock_a, recv_callback_a, NULL, NULL, &st); |
||||
uasync_add_socket_t(st.ua, st.sock_b, recv_callback_b, NULL, NULL, &st); |
||||
|
||||
/* Создаем dummynet */ |
||||
st.dn = dummynet_create(st.ua, "127.0.0.1", LISTEN_PORT); |
||||
if (!st.dn) { |
||||
printf("[FAIL] dummynet_create failed\n"); |
||||
uasync_destroy(st.ua, 1); |
||||
return 0; |
||||
} |
||||
|
||||
/* Увеличиваем буферы dummynet сокета */ |
||||
socket_t dn_sock = dummynet_get_socket(st.dn); |
||||
if (dn_sock != SOCKET_INVALID) { |
||||
int rcvbuf = 4 * 1024 * 1024; /* 4MB receive buffer */ |
||||
int sndbuf = 4 * 1024 * 1024; /* 4MB send buffer */ |
||||
setsockopt(dn_sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); |
||||
setsockopt(dn_sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); |
||||
} |
||||
|
||||
/* Настраиваем направления */ |
||||
dummynet_set_direction(st.dn, DUMMYNET_FORWARD, |
||||
delay_fixed_ms, delay_random_ms, |
||||
bandwidth_kbps, max_queue_pkts, loss_permille, |
||||
"127.0.0.1", LISTEN_PORT + 1); |
||||
|
||||
dummynet_set_direction(st.dn, DUMMYNET_BACKWARD, |
||||
delay_fixed_ms, delay_random_ms, |
||||
bandwidth_kbps, max_queue_pkts, loss_permille, |
||||
"127.0.0.1", LISTEN_PORT - 1); |
||||
|
||||
/* Быстрая отправка через uasync - 1 пакет каждые 10 мкс (100 pkt/ms) */ |
||||
printf("Sending %d packets (fast)...\n", PKT_COUNT); |
||||
|
||||
/* Запускаем таймер отправки с интервалом 1 timebase (0.1ms) */ |
||||
st.send_timer = uasync_set_timeout(st.ua, 1, &st, send_timer_callback); |
||||
|
||||
/* Умное ожидание завершения: ранний выход при готовности + watchdog */ |
||||
uint64_t start_time = get_time_us(); |
||||
uint64_t watchdog_start = 0; /* Стартует после отправки всех пакетов */ |
||||
int watchdog_active = 0; |
||||
const uint64_t WATCHDOG_US = 3000000ULL; /* 3 секунды максимум после отправки */ |
||||
|
||||
printf("Running (watchdog: 3s after send completion)...\n"); |
||||
|
||||
/* Основной цикл - только uasync_poll */ |
||||
while (1) { |
||||
uasync_poll(st.ua, 1); /* 0.1ms timeout - чаще для обработки таймеров */ |
||||
|
||||
uint64_t now = get_time_us(); |
||||
|
||||
/* Активируем watchdog когда все пакеты отправлены */ |
||||
if (!watchdog_active && st.next_seq >= PKT_COUNT) { |
||||
watchdog_start = now; |
||||
watchdog_active = 1; |
||||
} |
||||
|
||||
/* Проверяем условия завершения (только после отправки всех пакетов) */ |
||||
if (watchdog_active) { |
||||
uint64_t dset_fw, dfire_fw, sset_fw, sfire_fw; |
||||
uint64_t dset_bw, dfire_bw, sset_bw, sfire_bw; |
||||
dummynet_get_debug_counters(st.dn, DUMMYNET_FORWARD, &dset_fw, &dfire_fw, &sset_fw, &sfire_fw); |
||||
dummynet_get_debug_counters(st.dn, DUMMYNET_BACKWARD, &dset_bw, &dfire_bw, &sset_bw, &sfire_bw); |
||||
|
||||
/* Условие успешного завершения: все таймеры сработали + получили 95% пакетов */ |
||||
int timers_done = (dfire_fw >= dset_fw && dfire_bw >= dset_bw); |
||||
int packets_received = (st.recv_a >= PKT_COUNT * 0.95 && st.recv_b >= PKT_COUNT * 0.95); |
||||
|
||||
if (timers_done && packets_received) { |
||||
printf(" Completed successfully after %lu ms\n", (now - start_time) / 1000); |
||||
break; |
||||
} |
||||
|
||||
/* Watchdog: принудительный выход по таймауту */ |
||||
if ((now - watchdog_start) > WATCHDOG_US) { |
||||
printf(" [WARN] Watchdog timeout after %lu ms (timers=%s, recv=%u/%u)\n", |
||||
(now - start_time) / 1000, |
||||
timers_done ? "done" : "pending", |
||||
st.recv_a, st.recv_b); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Получаем статистику dummynet */ |
||||
const struct dummynet_stats* stats_fw = dummynet_get_stats(st.dn, DUMMYNET_FORWARD); |
||||
const struct dummynet_stats* stats_bw = dummynet_get_stats(st.dn, DUMMYNET_BACKWARD); |
||||
|
||||
/* Результаты с детальной диагностикой */ |
||||
int q_fw = dummynet_get_queue_size(st.dn, DUMMYNET_FORWARD); |
||||
int q_bw = dummynet_get_queue_size(st.dn, DUMMYNET_BACKWARD); |
||||
uint64_t dset_fw, dfire_fw, sset_fw, sfire_fw; |
||||
uint64_t dset_bw, dfire_bw, sset_bw, sfire_bw; |
||||
dummynet_get_debug_counters(st.dn, DUMMYNET_FORWARD, &dset_fw, &dfire_fw, &sset_fw, &sfire_fw); |
||||
dummynet_get_debug_counters(st.dn, DUMMYNET_BACKWARD, &dset_bw, &dfire_bw, &sset_bw, &sfire_bw); |
||||
|
||||
printf("Results:\n"); |
||||
printf(" Forward (A->B):\n"); |
||||
printf(" Test sent: %u\n", st.next_seq); |
||||
printf(" Dummynet recv:%" PRIu64 "\n", stats_fw->recv); |
||||
printf(" Delay timers: set=%" PRIu64 ", fire=%" PRIu64 "\n", dset_fw, dfire_fw); |
||||
printf(" Dummynet sent:%" PRIu64 "\n", stats_fw->sent); |
||||
printf(" Shaper timers: set=%" PRIu64 ", fire=%" PRIu64 "\n", sset_fw, sfire_fw); |
||||
printf(" Test recv: %u\n", st.recv_b); |
||||
printf(" Queue size: %d\n", q_fw); |
||||
printf(" Dropped: %" PRIu64 ", Lost: %" PRIu64 "\n", stats_fw->dropped, stats_fw->lost); |
||||
printf(" Backward (B->A):\n"); |
||||
printf(" Test sent: %u\n", st.next_seq); |
||||
printf(" Dummynet recv:%" PRIu64 "\n", stats_bw->recv); |
||||
printf(" Delay timers: set=%" PRIu64 ", fire=%" PRIu64 "\n", dset_bw, dfire_bw); |
||||
printf(" Dummynet sent:%" PRIu64 "\n", stats_bw->sent); |
||||
printf(" Shaper timers: set=%" PRIu64 ", fire=%" PRIu64 "\n", sset_bw, sfire_bw); |
||||
printf(" Test recv: %u\n", st.recv_a); |
||||
printf(" Queue size: %d\n", q_bw); |
||||
printf(" Dropped: %" PRIu64 ", Lost: %" PRIu64 "\n", stats_bw->dropped, stats_bw->lost); |
||||
|
||||
if (st.latency_count_b > 0) { |
||||
printf(" Latency A->B: avg=%.2f ms\n",
|
||||
(double)st.latency_sum_b / st.latency_count_b / 1000.0); |
||||
} |
||||
if (st.latency_count_a > 0) { |
||||
printf(" Latency B->A: avg=%.2f ms\n", |
||||
(double)st.latency_sum_a / st.latency_count_a / 1000.0); |
||||
} |
||||
|
||||
/* Проверка */ |
||||
int passed = 1; |
||||
|
||||
if (strcmp(name, "Baseline") == 0) { |
||||
/* Должны получить почти все */ |
||||
if (st.recv_b < PKT_COUNT * 0.95 || st.recv_a < PKT_COUNT * 0.95) { |
||||
printf(" [FAIL] Expected >95%% packets, got A=%u, B=%u\n", st.recv_a, st.recv_b); |
||||
passed = 0; |
||||
} else { |
||||
printf(" [PASS] >95%% packets received\n"); |
||||
} |
||||
}
|
||||
else if (strcmp(name, "Queue Overflow") == 0) { |
||||
/* Должны быть дропы */ |
||||
if (stats_fw->dropped < 100 && stats_bw->dropped < 100) { |
||||
printf(" [FAIL] Expected drops, got %" PRIu64 "/%" PRIu64 "\n", |
||||
stats_fw->dropped, stats_bw->dropped); |
||||
passed = 0; |
||||
} else { |
||||
printf(" [PASS] Queue overflow detected: %" PRIu64 "/%" PRIu64 " dropped\n", |
||||
stats_fw->dropped, stats_bw->dropped); |
||||
} |
||||
} |
||||
else if (strcmp(name, "Packet Loss") == 0) { |
||||
/* Должны быть потери ~5% */ |
||||
uint32_t lost_b = PKT_COUNT - st.recv_b; |
||||
uint32_t lost_a = PKT_COUNT - st.recv_a; |
||||
double loss_b = 100.0 * lost_b / PKT_COUNT; |
||||
double loss_a = 100.0 * lost_a / PKT_COUNT; |
||||
|
||||
if (loss_b < 2.0 || loss_b > 10.0 || loss_a < 2.0 || loss_a > 10.0) { |
||||
printf(" [FAIL] Expected ~5%% loss, got %.1f%%/%.1f%%\n", loss_a, loss_b); |
||||
passed = 0; |
||||
} else { |
||||
printf(" [PASS] Loss rate: %.1f%%/%.1f%% (expected ~5%%)\n", loss_a, loss_b); |
||||
} |
||||
} |
||||
else if (strcmp(name, "Delay") == 0) { |
||||
/* Проверяем задержку */ |
||||
if (st.latency_count_b > 0 && st.latency_count_a > 0) { |
||||
double avg_lat_b = (double)st.latency_sum_b / st.latency_count_b / 1000.0; |
||||
double avg_lat_a = (double)st.latency_sum_a / st.latency_count_a / 1000.0; |
||||
|
||||
/* Ожидаем ~60-80ms (50 + 0-20) */ |
||||
if (avg_lat_b >= 45.0 && avg_lat_b <= 85.0 && |
||||
avg_lat_a >= 45.0 && avg_lat_a <= 85.0) { |
||||
printf(" [PASS] Latency in expected range: %.1f/%.1f ms\n", avg_lat_a, avg_lat_b); |
||||
} else { |
||||
printf(" [FAIL] Latency out of range: %.1f/%.1f ms (expected 45-85ms)\n", |
||||
avg_lat_a, avg_lat_b); |
||||
passed = 0; |
||||
} |
||||
} else { |
||||
printf(" [FAIL] No packets received\n"); |
||||
passed = 0; |
||||
} |
||||
} |
||||
else if (strcmp(name, "Stress") == 0) { |
||||
/* В стрессе должно быть что-то дропнуто или потеряно */ |
||||
uint64_t total_dropped = stats_fw->dropped + stats_bw->dropped; |
||||
uint64_t total_lost = stats_fw->lost + stats_bw->lost; |
||||
|
||||
if (total_dropped == 0 && total_lost == 0) { |
||||
printf(" [FAIL] Expected drops or losses in stress test\n"); |
||||
passed = 0; |
||||
} else { |
||||
printf(" [PASS] Stress applied: %" PRIu64 " dropped, %" PRIu64 " lost\n", |
||||
total_dropped, total_lost); |
||||
} |
||||
} |
||||
|
||||
/* Очистка */ |
||||
dummynet_destroy(st.dn); |
||||
uasync_destroy(st.ua, 1); |
||||
|
||||
return passed; |
||||
} |
||||
|
||||
int main(void) { |
||||
printf("========================================\n"); |
||||
printf("Dummynet Comprehensive Test Suite\n"); |
||||
printf("(Single-threaded, uasync only)\n"); |
||||
printf("========================================\n"); |
||||
|
||||
srand((unsigned int)time(NULL)); |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_WARN); |
||||
socket_platform_init(); |
||||
|
||||
int passed = 0; |
||||
int failed = 0; |
||||
|
||||
/* Сценарий 1: Базовый (минимальная задержка, без потерь) */ |
||||
if (run_scenario("Baseline", 5, 2, 0, 1000, 0)) { |
||||
passed++; |
||||
} else { |
||||
failed++; |
||||
} |
||||
|
||||
/* Сценарий 2: Проверка задержки */ |
||||
if (run_scenario("Delay", 50, 20, 0, 1000, 0)) { |
||||
passed++; |
||||
} else { |
||||
failed++; |
||||
} |
||||
|
||||
/* Сценарий 3: Переполнение очереди */ |
||||
if (run_scenario("Queue Overflow", 100, 0, 100, 10, 0)) { |
||||
passed++; |
||||
} else { |
||||
failed++; |
||||
} |
||||
|
||||
/* Сценарий 4: Потери пакетов */ |
||||
if (run_scenario("Packet Loss", 5, 0, 0, 1000, 50)) { |
||||
passed++; |
||||
} else { |
||||
failed++; |
||||
} |
||||
|
||||
/* Сценарий 5: Стресс (все вместе) */ |
||||
if (run_scenario("Stress", 50, 30, 1000, 50, 10)) { |
||||
passed++; |
||||
} else { |
||||
failed++; |
||||
} |
||||
|
||||
printf("\n========================================\n"); |
||||
printf("Final Results: %d passed, %d failed\n", passed, failed); |
||||
printf("========================================\n"); |
||||
|
||||
socket_platform_cleanup(); |
||||
return failed > 0 ? 1 : 0; |
||||
} |
||||
@ -0,0 +1,198 @@
|
||||
#include <inttypes.h> |
||||
/**
|
||||
* @file test_dummynet_simple.c |
||||
* @brief Упрощённый тест модуля dummynet |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <sys/time.h> |
||||
#include <pthread.h> |
||||
|
||||
#include "../lib/u_async.h" |
||||
#include "../lib/socket_compat.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../src/dummynet.h" |
||||
|
||||
#define LISTEN_PORT 17000 |
||||
#define PKT_COUNT 100 |
||||
#define MAX_PKT_SIZE 1500 |
||||
|
||||
struct test_header { |
||||
uint32_t seq_num; |
||||
uint64_t send_time_us; |
||||
} __attribute__((packed)); |
||||
|
||||
static int tests_passed = 0; |
||||
static int tests_failed = 0; |
||||
|
||||
static inline uint64_t get_time_us(void) { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; |
||||
} |
||||
|
||||
static socket_t create_bound_socket(uint16_t port) { |
||||
socket_t sock = socket_create_udp(AF_INET); |
||||
if (sock == SOCKET_INVALID) return SOCKET_INVALID; |
||||
|
||||
struct sockaddr_in addr; |
||||
memset(&addr, 0, sizeof(addr)); |
||||
addr.sin_family = AF_INET; |
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
addr.sin_port = htons(port); |
||||
|
||||
int reuse = 1; |
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); |
||||
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) { |
||||
socket_close_wrapper(sock); |
||||
return SOCKET_INVALID; |
||||
} |
||||
return sock; |
||||
} |
||||
|
||||
/* Функция для запуска uasync в отдельном потоке */ |
||||
static void* uasync_thread(void* arg) { |
||||
struct UASYNC* ua = (struct UASYNC*)arg; |
||||
printf(" [uasync] Event loop started\n"); |
||||
|
||||
/* Запускаем цикл на 5 секунд */ |
||||
for (int i = 0; i < 500; i++) { |
||||
uasync_poll(ua, 10); /* 10 timebase = 1ms */ |
||||
usleep(1000); /* 1ms */ |
||||
} |
||||
|
||||
printf(" [uasync] Event loop finished\n"); |
||||
return NULL; |
||||
} |
||||
|
||||
int main(void) { |
||||
printf("========================================\n"); |
||||
printf("Dummynet Simple Test\n"); |
||||
printf("========================================\n\n"); |
||||
|
||||
srand((unsigned int)time(NULL)); |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_WARN); |
||||
socket_platform_init(); |
||||
|
||||
struct UASYNC* ua = uasync_create(); |
||||
if (!ua) { |
||||
printf("[FAIL] Failed to create uasync\n"); |
||||
return 1; |
||||
} |
||||
|
||||
/* Создаём сокеты */ |
||||
socket_t sock_a = create_bound_socket(LISTEN_PORT - 1); |
||||
socket_t sock_b = create_bound_socket(LISTEN_PORT + 1); |
||||
|
||||
if (sock_a == SOCKET_INVALID || sock_b == SOCKET_INVALID) { |
||||
printf("[FAIL] Failed to create sockets\n"); |
||||
return 1; |
||||
} |
||||
|
||||
/* Создаём dummynet с задержкой 50ms и jitter 20ms */ |
||||
struct dummynet* dn = dummynet_create(ua, "127.0.0.1", LISTEN_PORT); |
||||
if (!dn) { |
||||
printf("[FAIL] Failed to create dummynet\n"); |
||||
return 1; |
||||
} |
||||
|
||||
dummynet_set_direction(dn, DUMMYNET_FORWARD, |
||||
50, 20, /* delay: 50ms + 0..20ms */ |
||||
0, /* unlimited bandwidth */ |
||||
100, /* max queue */ |
||||
0, /* no loss */ |
||||
"127.0.0.1", LISTEN_PORT + 1); |
||||
|
||||
dummynet_set_direction(dn, DUMMYNET_BACKWARD, |
||||
50, 20, |
||||
0, |
||||
100, |
||||
0, |
||||
"127.0.0.1", LISTEN_PORT - 1); |
||||
|
||||
printf("Configuration:\n"); |
||||
printf(" Listen port: %d\n", LISTEN_PORT); |
||||
printf(" Delay: 50ms + 0-20ms\n"); |
||||
printf(" Bandwidth: unlimited\n"); |
||||
printf(" Queue size: 100 packets\n"); |
||||
printf(" Loss: 0%%\n\n"); |
||||
|
||||
/* Запускаем uasync в отдельном потоке */ |
||||
pthread_t thread; |
||||
pthread_create(&thread, NULL, uasync_thread, ua); |
||||
|
||||
/* Отправляем пакеты */ |
||||
printf("Sending %d packets A->B and B->A...\n", PKT_COUNT); |
||||
|
||||
uint8_t buf[sizeof(struct test_header) + 100]; |
||||
struct test_header* hdr = (struct test_header*)buf; |
||||
|
||||
for (int i = 0; i < PKT_COUNT; i++) { |
||||
/* A -> B */ |
||||
hdr->seq_num = i; |
||||
hdr->send_time_us = get_time_us(); |
||||
|
||||
struct sockaddr_in dest; |
||||
memset(&dest, 0, sizeof(dest)); |
||||
dest.sin_family = AF_INET; |
||||
dest.sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
dest.sin_port = htons(LISTEN_PORT); |
||||
|
||||
socket_sendto(sock_a, buf, sizeof(buf), (struct sockaddr*)&dest, sizeof(dest)); |
||||
|
||||
/* B -> A */ |
||||
hdr->seq_num = i + 1000; |
||||
hdr->send_time_us = get_time_us(); |
||||
socket_sendto(sock_b, buf, sizeof(buf), (struct sockaddr*)&dest, sizeof(dest)); |
||||
|
||||
usleep(1000); /* 1ms между пакетами */ |
||||
} |
||||
|
||||
printf("Waiting for packets...\n"); |
||||
|
||||
/* Ждём завершения потока uasync */ |
||||
pthread_join(thread, NULL); |
||||
|
||||
/* Получаем статистику */ |
||||
const struct dummynet_stats* stats_fw = dummynet_get_stats(dn, DUMMYNET_FORWARD); |
||||
const struct dummynet_stats* stats_bw = dummynet_get_stats(dn, DUMMYNET_BACKWARD); |
||||
|
||||
printf("\nResults:\n"); |
||||
printf(" Forward (A->B): recv=%llu, sent=%llu, dropped=%llu\n", |
||||
stats_fw->recv, stats_fw->sent, stats_fw->dropped); |
||||
printf(" Backward (B->A): recv=%llu, sent=%llu, dropped=%llu\n", |
||||
stats_bw->recv, stats_bw->sent, stats_bw->dropped); |
||||
|
||||
/* Проверяем результаты */ |
||||
int passed = 1; |
||||
|
||||
if (stats_fw->recv < PKT_COUNT * 0.9 || stats_bw->recv < PKT_COUNT * 0.9) { |
||||
printf("\n[FAIL] Expected >90%% packets received\n"); |
||||
printf(" Forward: %llu/%d, Backward: %llu/%d\n", |
||||
stats_fw->recv, PKT_COUNT, stats_bw->recv, PKT_COUNT); |
||||
passed = 0; |
||||
tests_failed++; |
||||
} else { |
||||
printf("\n[PASS] >90%% packets received successfully\n"); |
||||
tests_passed++; |
||||
} |
||||
|
||||
/* Очистка */ |
||||
dummynet_destroy(dn); |
||||
socket_close_wrapper(sock_a); |
||||
socket_close_wrapper(sock_b); |
||||
uasync_destroy(ua, 1); |
||||
socket_platform_cleanup(); |
||||
|
||||
printf("\n========================================\n"); |
||||
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed); |
||||
printf("========================================\n"); |
||||
|
||||
return tests_failed > 0 ? 1 : 0; |
||||
} |
||||
@ -0,0 +1,289 @@
|
||||
/**
|
||||
* @file test_etcp_dummynet.c |
||||
* @brief ETCP + dummynet integration test (programmatic config) |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdint.h> |
||||
#include <sys/time.h> |
||||
#include <unistd.h> |
||||
#include <arpa/inet.h> |
||||
|
||||
#include "../lib/u_async.h" |
||||
#include "../lib/ll_queue.h" |
||||
#include "../lib/memory_pool.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/platform_compat.h" |
||||
#include "../src/dummynet.h" |
||||
#include "../src/config_parser.h" |
||||
#include "../src/utun_instance.h" |
||||
#include "../src/etcp.h" |
||||
#include "../src/etcp_api.h" |
||||
#include "../src/etcp_connections.h" |
||||
#include "../src/secure_channel.h" |
||||
#include "../src/config_updater.h" |
||||
#include "../src/routing.h" |
||||
#include "../src/crc32.h" |
||||
|
||||
#define SEND_MS 1000 |
||||
#define WAIT_MS 1000 |
||||
#define PAYLOAD_SIZE 500 |
||||
#define DNET_PORT 29001 |
||||
#define SERVER_PORT 29002 |
||||
#define CLIENT_PORT 29000 |
||||
|
||||
struct test_pkt { |
||||
uint32_t seq; |
||||
uint32_t crc; |
||||
uint8_t data[PAYLOAD_SIZE]; |
||||
}; |
||||
|
||||
static struct UASYNC* ua = NULL; |
||||
static struct dummynet* dn = NULL; |
||||
static struct UTUN_INSTANCE* server = NULL; |
||||
static struct UTUN_INSTANCE* client = NULL; |
||||
|
||||
static uint32_t seq_num = 0, pkts_sent = 0, pkts_received = 0; |
||||
static uint32_t pkts_ooo = 0, pkts_corrupt = 0; |
||||
static uint32_t last_seq = 0; |
||||
static int first_pkt = 0; |
||||
static uint64_t start_us = 0, latency_sum = 0; |
||||
static int sending_done = 0; |
||||
|
||||
static uint64_t now_us(void) { |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; |
||||
} |
||||
|
||||
static void on_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||
(void)conn; |
||||
if (!entry || entry->len < sizeof(struct test_pkt)) { |
||||
if (entry) queue_entry_free(entry); |
||||
return; |
||||
} |
||||
struct test_pkt* pkt = (struct test_pkt*)entry->dgram; |
||||
if (crc32_calc(pkt->data, PAYLOAD_SIZE) != pkt->crc) pkts_corrupt++; |
||||
if (!first_pkt) { first_pkt = 1; last_seq = pkt->seq; } |
||||
else { if (pkt->seq != last_seq + 1) pkts_ooo++; last_seq = pkt->seq; } |
||||
pkts_received++; |
||||
latency_sum += now_us() - start_us; |
||||
queue_entry_free(entry); |
||||
} |
||||
|
||||
static void send_pkt(void* arg) { |
||||
(void)arg; |
||||
if (sending_done || (now_us() - start_us)/1000 >= SEND_MS) { |
||||
sending_done = 1; return; |
||||
} |
||||
if (!client || !client->connections) return; |
||||
|
||||
struct ll_entry* e = ll_alloc_lldgram(sizeof(struct test_pkt)); |
||||
if (!e) return; |
||||
struct test_pkt* pkt = (struct test_pkt*)e->dgram; |
||||
pkt->seq = seq_num++; |
||||
for (int i = 0; i < PAYLOAD_SIZE; i++) pkt->data[i] = (pkt->seq + i) & 0xFF; |
||||
pkt->crc = crc32_calc(pkt->data, PAYLOAD_SIZE); |
||||
e->len = sizeof(struct test_pkt); |
||||
|
||||
if (etcp_send(client->connections, e) == 0) pkts_sent++; |
||||
else queue_entry_free(e); |
||||
|
||||
/* Schedule next packet with 5ms delay for rate limiting (~200 pkt/sec)
|
||||
* Note: uasync_set_timeout uses 0.1ms units, so 50 = 5ms */ |
||||
uasync_set_timeout(ua, 50, NULL, send_pkt); |
||||
} |
||||
|
||||
static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id, |
||||
const char* priv_hex, const char* pub_hex) { |
||||
struct UTUN_INSTANCE* inst = calloc(1, sizeof(*inst)); |
||||
if (!inst) return NULL; |
||||
|
||||
inst->ua = u; |
||||
inst->node_id = node_id; |
||||
|
||||
/* Инициализируем ключи из hex строк */ |
||||
if (sc_init_local_keys(&inst->my_keys, pub_hex, priv_hex) != SC_OK) { |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET)); |
||||
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE); |
||||
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE); |
||||
|
||||
if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) { |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
struct utun_config* cfg = calloc(1, sizeof(*cfg)); |
||||
if (!cfg) { |
||||
free(inst); |
||||
return NULL; |
||||
} |
||||
|
||||
strncpy(cfg->global.my_public_key_hex, pub_hex, MAX_KEY_LEN-1); |
||||
strncpy(cfg->global.my_private_key_hex, priv_hex, MAX_KEY_LEN-1); |
||||
cfg->global.my_node_id = node_id; |
||||
cfg->global.mtu = 1400; |
||||
|
||||
inst->config = cfg; |
||||
return inst; |
||||
} |
||||
|
||||
static int add_server(struct UTUN_INSTANCE* inst, const char* name, int port) { |
||||
struct CFG_SERVER* srv = calloc(1, sizeof(*srv)); |
||||
if (!srv) return -1; |
||||
strncpy(srv->name, name, MAX_CONN_NAME_LEN-1); |
||||
srv->ip.ss_family = AF_INET; |
||||
((struct sockaddr_in*)&srv->ip)->sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
((struct sockaddr_in*)&srv->ip)->sin_port = htons(port); |
||||
srv->type = CFG_SERVER_TYPE_PUBLIC; |
||||
srv->next = inst->config->servers; |
||||
inst->config->servers = srv; |
||||
return 0; |
||||
} |
||||
|
||||
static int add_client(struct UTUN_INSTANCE* inst, struct UTUN_INSTANCE* srv_inst, |
||||
const char* name, const char* peer_pubkey, int remote_port) { |
||||
struct CFG_CLIENT* cli = calloc(1, sizeof(*cli)); |
||||
if (!cli) return -1; |
||||
strncpy(cli->name, name, MAX_CONN_NAME_LEN-1); |
||||
strncpy(cli->peer_public_key_hex, peer_pubkey, MAX_KEY_LEN-1); |
||||
cli->keepalive = 1; |
||||
|
||||
/* Находим локальный сервер */ |
||||
struct CFG_SERVER* local_srv = inst->config->servers; |
||||
|
||||
struct CFG_CLIENT_LINK* link = calloc(1, sizeof(*link)); |
||||
link->remote_addr.ss_family = AF_INET; |
||||
((struct sockaddr_in*)&link->remote_addr)->sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
((struct sockaddr_in*)&link->remote_addr)->sin_port = htons(remote_port); |
||||
link->local_srv = local_srv; |
||||
strcpy(link->server_name, local_srv ? local_srv->name : "local"); |
||||
|
||||
cli->links = link; |
||||
cli->next = inst->config->clients; |
||||
inst->config->clients = cli; |
||||
return 0; |
||||
} |
||||
|
||||
int main(void) { |
||||
printf("=== ETCP + Dummynet Test ===\n\n"); |
||||
srand((unsigned)time(NULL)); |
||||
debug_config_init(); |
||||
// debug_set_level(DEBUG_LEVEL_WARN);
|
||||
debug_set_categories(DEBUG_CATEGORY_ALL & ~DEBUG_CATEGORY_UASYNC & ~DEBUG_CATEGORY_TIMERS & ~DEBUG_CATEGORY_CRYPTO); // Enable all except uasync
|
||||
socket_platform_init(); |
||||
crc32_init(); |
||||
|
||||
ua = uasync_create(); |
||||
if (!ua) { printf("uasync failed\n"); return 1; } |
||||
|
||||
printf("Using fixed keys...\n"); |
||||
/* These keys are copied from test_etcp_two_instances.c and are known to work */ |
||||
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb"; |
||||
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9"; |
||||
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2"; |
||||
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71"; |
||||
|
||||
printf("Creating instances...\n"); |
||||
server = create_instance(ua, 0x1111111111111111ULL, s_priv, s_pub); |
||||
client = create_instance(ua, 0x2222222222222222ULL, c_priv, c_pub); |
||||
if (!server || !client) { printf("Instance failed\n"); return 1; } |
||||
|
||||
/* Server only listens - no client config needed
|
||||
* Client needs both a local server (for receiving) and a client config (for connecting to dummynet) */ |
||||
if (add_server(server, "srv", SERVER_PORT) < 0 || |
||||
add_server(client, "local", CLIENT_PORT) < 0 || |
||||
add_client(client, server, "peer", s_pub, DNET_PORT) < 0) { |
||||
printf("Config failed\n"); return 1; |
||||
} |
||||
|
||||
printf("Init server...\n"); |
||||
if (utun_instance_init(server) < 0) { printf("Server init failed\n"); return 1; } |
||||
|
||||
printf("Init client...\n"); |
||||
if (utun_instance_init(client) < 0) { printf("Client init failed\n"); return 1; } |
||||
|
||||
etcp_bind(server, 0, on_recv); |
||||
|
||||
printf("Creating dummynet...\n"); |
||||
dn = dummynet_create(ua, "127.0.0.1", DNET_PORT); |
||||
if (!dn) { printf("Dummynet failed\n"); return 1; } |
||||
dummynet_set_direction(dn, 0, 30, 10, 0, 1000, 0, "127.0.0.1", SERVER_PORT);// dn, dir, delay, delay rnd, bw kbps, max_pkts, loss
|
||||
dummynet_set_direction(dn, 1, 30, 10, 0, 1000, 0, "127.0.0.1", CLIENT_PORT); |
||||
|
||||
/* Wait for connection to establish */ |
||||
printf("Waiting for connection establishment...\n"); |
||||
uint64_t connect_start = now_us(); |
||||
int connected = 0; |
||||
while ((now_us() - connect_start) < 5000000ULL) { /* 5 second timeout */ |
||||
uasync_poll(ua, 1); |
||||
|
||||
/* Check if client has an established connection with ready links */ |
||||
if (client && client->connections) { |
||||
struct ETCP_CONN* conn = client->connections; |
||||
/* Check if peer_node_id is set (non-zero) and has links */ |
||||
if (conn->peer_node_id != 0 && conn->links != NULL) { |
||||
struct ETCP_LINK* link = conn->links; |
||||
int ready_links = 0; |
||||
while (link) { |
||||
if (link->initialized) ready_links++; |
||||
link = link->next; |
||||
} |
||||
if (ready_links > 0) { |
||||
/* Print detailed link info */ |
||||
link = conn->links; |
||||
while (link) { |
||||
printf("Link: init=%u status=%u timer=%p\n", |
||||
link->initialized, link->link_status, link->shaper_timer); |
||||
link = link->next; |
||||
} |
||||
connected = 1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!connected) { |
||||
printf("Connection failed to establish within timeout\n"); |
||||
/* Continue anyway to see what happens */ |
||||
} else { |
||||
printf("Connection established\n"); |
||||
/* Give extra time for link_status to stabilize */ |
||||
uint64_t wait_start = now_us(); |
||||
while ((now_us() - wait_start) < 100000ULL) { /* 100ms */ |
||||
uasync_poll(ua, 1); |
||||
} |
||||
} |
||||
|
||||
printf("\nSending %d ms...\n", SEND_MS); |
||||
start_us = now_us(); |
||||
send_pkt(NULL); |
||||
|
||||
uint64_t t = now_us(); |
||||
while ((now_us() - t) < (uint64_t)(SEND_MS + WAIT_MS) * 1000) { |
||||
uasync_poll(ua, 1); |
||||
if (sending_done && pkts_received >= pkts_sent * 0.95) break; |
||||
} |
||||
|
||||
printf("\n=== Results ===\n"); |
||||
printf("Sent: %u Received: %u (%.1f%%)\n", pkts_sent, pkts_received, |
||||
100.0 * pkts_received / (pkts_sent ? pkts_sent : 1)); |
||||
printf("Out of order: %u Corrupted: %u\n", pkts_ooo, pkts_corrupt); |
||||
if (pkts_received > 0) printf("Avg latency: %.2f ms\n", latency_sum / (double)pkts_received / 1000.0); |
||||
|
||||
int pass = (pkts_ooo == 0 && pkts_corrupt == 0 && pkts_received >= pkts_sent * 0.90); |
||||
printf("\n%s\n", pass ? "[PASS]" : "[FAIL]"); |
||||
|
||||
dummynet_destroy(dn); |
||||
utun_instance_destroy(server); |
||||
utun_instance_destroy(client); |
||||
uasync_destroy(ua, 1); |
||||
return pass ? 0 : 1; |
||||
} |
||||
Loading…
Reference in new issue