Browse Source

dummynet: route between explicit independently bound endpoints

master
evgeny 2 days ago
parent
commit
12d4e2433e
  1. 39
      src/transport_layer/dummynet.c
  2. 13
      src/transport_layer/dummynet.h
  3. 23
      tests/bbr_integration/test_bbr_integration.c
  4. 67
      tests/test_etcp_router.c

39
src/transport_layer/dummynet.c

@ -110,12 +110,15 @@ static inline uint32_t random_range(uint32_t max) {
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;
/* Источник одного направления — endpoint назначения обратного направления. */
static int dummynet_get_direction(struct dummynet* dn, const struct sockaddr_storage* source) {
if (source->ss_family != AF_INET) return -1;
const struct sockaddr_in* src = (const struct sockaddr_in*)source;
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) {
struct dummynet_dir* reverse = &dn->dirs[1 - i];
const struct sockaddr_in* peer = (const struct sockaddr_in*)&reverse->dest_addr;
if (reverse->configured && peer->sin_family == AF_INET &&
peer->sin_addr.s_addr == src->sin_addr.s_addr && peer->sin_port == src->sin_port) return i;
}
return -1;
}
@ -201,17 +204,11 @@ static void dummynet_read_callback(socket_t sock, void* user_arg) {
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);
int dir_idx = dummynet_get_direction(dn, &src_addr);
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);
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Packet from unknown endpoint %s, expected %s or %s",
sockaddr_storage_to_str(&src_addr).str, sockaddr_storage_to_str(&dn->dirs[DUMMYNET_BACKWARD].dest_addr).str,
sockaddr_storage_to_str(&dn->dirs[DUMMYNET_FORWARD].dest_addr).str);
return;
}
@ -340,7 +337,7 @@ static void dummynet_delay_callback(void* user_arg) {
/* Создаёт 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) {
if (!ua) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Invalid parameters");
return NULL;
}
@ -408,6 +405,8 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
dummynet_destroy(dn);
return NULL;
}
dn->listen_port = socket_get_bound_port(dn->sock);
if (!dn->listen_port) { dummynet_destroy(dn); return NULL; }
/* Добавляем сокет в uasync */
void* sock_id = uasync_add_socket_t(ua, dn->sock, dummynet_read_callback, NULL, NULL, "dummynet", dn);
@ -417,10 +416,8 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
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);
DEBUG_INFO(DEBUG_CATEGORY_DUMMYNET, "Dummynet created on %s:%u; configure both peer endpoints before traffic",
bind_ip ? bind_ip : "0.0.0.0", dn->listen_port);
return dn;
}

13
src/transport_layer/dummynet.h

@ -4,7 +4,7 @@
*
* Эмулирует сетевые условия: задержку (fixed + random), ограничение пропускной способности
* и случайные потери пакетов. Работает как "человек посередине" - принимает пакеты на
* listen_port и пересылает между port-1 и port+1 с эмуляцией условий канала.
* listen_port и пересылает между двумя явно настроенными endpoint с эмуляцией условий канала.
*
* Архитектура:
* [UDP recv] -> [delay timer] -> [check queue/drop] -> [ll_queue] -> [shaper timer] -> [UDP send]
@ -13,8 +13,9 @@
*
* Поток данных:
* - Слушаем на bind_ip:listen_port
* - Пакеты от listen_port-1 -> отправляем на dest_forward (forward direction)
* - Пакеты от listen_port+1 -> отправляем на dest_backward (backward direction)
* - Пакеты от dest_backward -> отправляем на dest_forward (forward direction)
* - Пакеты от dest_forward -> отправляем на dest_backward (backward direction)
* - Оба направления настраиваются до начала трафика; IP и порты участников независимы
* - Каждое направление имеет свою очередь и параметры эмуляции
*/
@ -36,8 +37,8 @@ struct ll_queue;
struct ll_entry;
/* Направления передачи */
#define DUMMYNET_FORWARD 0 // port-1 -> port+1
#define DUMMYNET_BACKWARD 1 // port+1 -> port-1
#define DUMMYNET_FORWARD 0 // client -> server
#define DUMMYNET_BACKWARD 1 // server -> client
#define DUMMYNET_DIR_COUNT 2
/* Максимальный размер UDP пакета */
@ -71,7 +72,7 @@ struct dummynet;
*
* @param ua Экземпляр uasync (обязателен)
* @param bind_ip IP адрес для привязки (NULL или "0.0.0.0" для всех интерфейсов)
* @param listen_port Порт для прослушивания (средний порт, port-1 и port+1 - клиенты)
* @param listen_port Порт для прослушивания (0 выбирает ОС; фактический порт — get_listen_port)
* @return Указатель на контекст или NULL при ошибке
*/
struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_t listen_port);

23
tests/bbr_integration/test_bbr_integration.c

@ -36,12 +36,9 @@
#include "../src/config_updater.h"
#include "routing.h"
#include "crc32.h"
#include "test_net.h"
/* ===== Test constants ===== */
#define DN_PORT 21001
#define SRV_PORT 21002
#define CLI_PORT 21000
#define PAYLOAD_SIZE 1200
#define TEST_DURATION_MS 3500
#define METRICS_TB 1000 /* 100ms in 0.1ms units */
@ -343,12 +340,14 @@ int main(void) {
ctx.sender->test_user_ptr = &ctx;
ctx.receiver->test_user_ptr = &ctx;
if (add_server(ctx.receiver, "srv", SRV_PORT) < 0 ||
add_server(ctx.sender, "local", CLI_PORT) < 0 ||
if (add_server(ctx.receiver, "srv", 0) < 0 ||
add_server(ctx.sender, "local", 0) < 0 ||
add_client(ctx.sender, s_pub) < 0) {
printf("ERROR: config setup failed\n"); return 1;
}
add_link(ctx.sender->config->clients, ctx.sender->config->servers, DN_PORT);
ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", 0);
if (!ctx.dn) { printf("ERROR: dummynet_create failed\n"); return 1; }
add_link(ctx.sender->config->clients, ctx.sender->config->servers, dummynet_get_listen_port(ctx.dn));
printf("Init receiver...\n");
if (utun_instance_init(ctx.receiver) < 0) { printf("ERROR: receiver init failed\n"); return 1; }
@ -357,16 +356,16 @@ int main(void) {
etcp_bind(ctx.receiver, ETCP_RT_ID_DATA, on_recv);
printf("Creating dummynet on port %d ...\n", DN_PORT);
ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", DN_PORT);
if (!ctx.dn) { printf("ERROR: dummynet_create failed\n"); return 1; }
int receiver_port = ss_get_port(&test_etcp_bound_socket(ctx.receiver, "srv")->local_addr);
int sender_port = ss_get_port(&test_etcp_bound_socket(ctx.sender, "local")->local_addr);
printf("Ports assigned: client=%d dummynet=%u server=%d\n", sender_port, dummynet_get_listen_port(ctx.dn), receiver_port);
dummynet_set_direction(ctx.dn, DUMMYNET_FORWARD,
EMU_DELAY_MS, EMU_JITTER_MS,
EMU_BW_KBPS, EMU_QUEUE_PKTS, EMU_LOSS_PERM,
"127.0.0.1", SRV_PORT);
"127.0.0.1", (uint16_t)receiver_port);
dummynet_set_direction(ctx.dn, DUMMYNET_BACKWARD,
5, 1, 0, 200, 0,
"127.0.0.1", CLI_PORT);
"127.0.0.1", (uint16_t)sender_port);
/* Wait for connection */
printf("Waiting for connection...\n");

67
tests/test_etcp_router.c

@ -12,6 +12,7 @@
#include "../lib/platform_compat.h"
#include "../lib/socket_compat.h"
#include "test_utils.h"
#include "test_net.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
@ -125,40 +126,6 @@ static double toc_ms(const struct timespec* start) {
return (end.tv_sec - start->tv_sec) * 1000.0 + (end.tv_nsec - start->tv_nsec) / 1000000.0;
}
// ======================== Port allocation ========================
static int alloc_consecutive_ports(int* base_port) {
for (int attempt = 0; attempt < 200; attempt++) {
socket_t s = socket_create_udp(AF_INET);
if (s == SOCKET_INVALID) return -1;
struct sockaddr_in a;
memset(&a, 0, sizeof(a));
a.sin_family = AF_INET;
a.sin_addr.s_addr = inet_addr("127.0.0.1");
a.sin_port = 0; // auto-assign
if (bind(s, (struct sockaddr*)&a, sizeof(a)) != 0) { socket_close_wrapper(s); continue; }
socklen_t alen = sizeof(a);
getsockname(s, (struct sockaddr*)&a, &alen);
int port = ntohs(a.sin_port);
socket_close_wrapper(s);
// Need port-1, port, port+1 all free
int ok = 1;
for (int p = port - 1; p <= port + 1 && ok; p++) {
if (p < 1024) { ok = 0; break; }
socket_t ts = socket_create_udp(AF_INET);
if (ts == SOCKET_INVALID) { ok = 0; break; }
struct sockaddr_in ta;
memset(&ta, 0, sizeof(ta));
ta.sin_family = AF_INET;
ta.sin_addr.s_addr = inet_addr("127.0.0.1");
ta.sin_port = htons((uint16_t)p);
if (bind(ts, (struct sockaddr*)&ta, sizeof(ta)) != 0) ok = 0;
socket_close_wrapper(ts);
}
if (ok) { *base_port = port; return 0; }
}
return -1;
}
// ======================== Config generation ========================
static const char* srv_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
static const char* srv_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
@ -393,8 +360,7 @@ static int test_loopback(void) {
// ======================== Dummynet setup ========================
static int setup_dummynet(void) {
dn = dummynet_create(ua, "127.0.0.1", (uint16_t)g_dn_port);
if (!dn) { printf("[FAIL] dummynet_create on port %d\n", g_dn_port); return -1; }
if (!dn) { printf("[FAIL] dummynet listener missing\n"); return -1; }
// Forward: dummynet → server
dummynet_set_direction(dn, DUMMYNET_FORWARD, 0, 0, 0, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_srv_port);
@ -694,20 +660,6 @@ int main(void) {
if (!g_reset_mode) g_reset_mode = g_tcp ? "both" : "client";
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp fail\n"); return 1; }
// Allocate consecutive ports
int base_port;
if (alloc_consecutive_ports(&base_port) != 0) {
printf("[FAIL] cannot allocate consecutive ports\n");
test_rmdir(temp_dir);
return 1;
}
g_dn_port = base_port;
g_srv_port = base_port + 1;
g_cli_port = base_port - 1;
printf("Ports: dummynet=%d server=%d client=%d\n", g_dn_port, g_srv_port, g_cli_port);
write_configs();
printf("=== test_etcp_router (%s, bounded backpressure and transport recovery) ===\n", g_tcp ? "TCP" : "UDP");
debug_config_init();
@ -725,11 +677,23 @@ int main(void) {
tic(&t_phase_start);
ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); goto done; }
if (!g_tcp) {
dn = dummynet_create(ua, "127.0.0.1", 0);
if (!dn) { printf("[FAIL] dummynet create\n"); goto done; }
g_dn_port = dummynet_get_listen_port(dn);
}
write_configs();
srv = utun_instance_create(ua, server_conf);
if (!srv || utun_instance_init(srv) < 0) { printf("[FAIL] server create\n"); goto done; }
cli = utun_instance_create(ua, client_conf);
if (cli) {
g_srv_port = ss_get_port(&test_etcp_bound_socket(srv, "s1")->local_addr);
g_cli_port = ss_get_port(&test_etcp_bound_socket(cli, "s1")->local_addr);
if (!g_tcp && setup_dummynet() != 0) { printf("[FAIL] dummynet setup\n"); goto done; }
printf("Ports: dummynet=%d server=%d client=%d\n", g_dn_port, g_srv_port, g_cli_port);
}
if (!cli || utun_instance_init(cli) < 0) { printf("[FAIL] client create\n"); goto done; }
server_node_id = srv->node_id; client_node_id = cli->node_id;
@ -743,9 +707,6 @@ int main(void) {
}
}
// Create dummynet between client and server
if (!g_tcp && setup_dummynet() != 0) { printf("[FAIL] dummynet setup\n"); goto done; }
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout_cb, "to");

Loading…
Cancel
Save