From 39544e6b2be5b8df20b675c610e02fba857bcb72 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 12:38:05 +0300 Subject: [PATCH] Fix: Add bounds/mtu checks, underflow protection and null deref fix for ETCP robustness --- lib/myip.c | 236 ++++++++++++++++++++++++++++++++++++++++ src/etcp.c | 25 ++++- src/etcp_connections.c | 25 +++++ src/etcp_connections.h | 1 + src/etcp_loadbalancer.c | 3 +- src/route_p2pconn.txt | 11 ++ utun.md | 218 +++++++++++++++++++++++++++++++++++++ 7 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 lib/myip.c create mode 100644 src/route_p2pconn.txt create mode 100644 utun.md diff --git a/lib/myip.c b/lib/myip.c new file mode 100644 index 00000000..6693aa68 --- /dev/null +++ b/lib/myip.c @@ -0,0 +1,236 @@ +/* outgoing_iface.c + * + * Определяет IPv4-адрес и индекс/имя исходящего интерфейса, + * который ОС выберет при отправке на заданный destination + * после bind на 0.0.0.0 (INADDR_ANY). + * + * Сборка: + * Linux / BSD: gcc -o out out.c + * Windows (MSVC): cl out.c /link ws2_32.lib iphlpapi.lib + * Windows (MinGW): gcc -o out.exe out.c -lws2_32 -liphlpapi + */ + +#include +#include +#include +#include + +/* ===================== Платформенные заголовки ===================== */ +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# include +# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "iphlpapi.lib") + + typedef SOCKET socket_t; +# define SOCKET_INVALID INVALID_SOCKET +# define SOCKET_ERRNO WSAGetLastError() + + /* На Windows нет IFNAMSIZ; берём с запасом */ +# ifndef IF_NAMESIZE +# define IF_NAMESIZE 256 +# endif + +#else /* Linux, FreeBSD, OpenBSD, NetBSD, macOS, … */ +# include +# include +# include +# include +# include +# include +# include +# include + + typedef int socket_t; +# define SOCKET_INVALID (-1) +# define SOCKET_ERRNO errno + +# ifndef IF_NAMESIZE +# define IF_NAMESIZE IFNAMSIZ /* обычно 16 */ +# endif +#endif + +/* ===================== Структура результата ===================== */ +typedef struct { + char ip[INET_ADDRSTRLEN]; /* IP строкой, например "192.168.1.5" */ + char name[IF_NAMESIZE]; /* имя интерфейса: "eth0", "em0", "Wi-Fi" … */ + uint32_t ifindex; /* числовой ifindex (if_nametoindex) */ +} iface_info_t; + +/* ===================== Утилиты ===================== */ +static int net_init(void) +{ +#ifdef _WIN32 + static int done = 0; + if (!done) { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + return -1; + done = 1; + } +#endif + return 0; +} + +static void socket_close(socket_t s) +{ +#ifdef _WIN32 + if (s != SOCKET_INVALID) + closesocket(s); +#else + if (s >= 0) + close(s); +#endif +} + +/* ===================== Основная функция ===================== */ +/* + * dst_ip — адрес назначения (например, "8.8.8.8" или IP вашего VPN-сервера) + * dst_port — любой открытый порт (например, 53 или 123), реальных пакетов не уйдёт + * info — заполняемая структура + * + * Возвращает 0 при успехе, -1 при ошибке. + */ +int get_outgoing_iface(const char *dst_ip, int dst_port, iface_info_t *info) +{ + socket_t sock = SOCKET_INVALID; + int ret = -1; + struct sockaddr_in dst, src; + socklen_t src_len = sizeof(src); + + memset(info, 0, sizeof(*info)); + if (net_init() != 0) + return -1; + + /* 1. UDP-сокет */ + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == SOCKET_INVALID) + return -1; + + /* 2. Bind на 0.0.0.0 — именно так, как в вопросе */ + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_addr.s_addr = INADDR_ANY; /* 0.0.0.0 */ + dst.sin_port = 0; /* система выберет порт */ + + if (bind(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0) + goto end; + + /* 3. "Соединяемся" с destination. + * Для UDP это НЕ шлёт реальных пакетов, но ядро просчитывает + * маршрут и фиксирует выбранный source IP в сокете. */ + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_port = htons((uint16_t)dst_port); + if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1) + goto end; + if (connect(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0) + goto end; + + /* 4. Забираем выбранный локальный адрес */ + if (getsockname(sock, (struct sockaddr *)&src, &src_len) != 0) + goto end; + if (!inet_ntop(AF_INET, &src.sin_addr, info->ip, sizeof(info->ip))) + goto end; + + /* 5. По source IP ищем интерфейс в системном списке */ +#ifdef _WIN32 + { + ULONG size = 0; + DWORD rv; + PIP_ADAPTER_ADDRESSES adapters = NULL, a; + PIP_ADAPTER_UNICAST_ADDRESS u; + + /* первый вызов — узнать размер буфера */ + rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, + NULL, NULL, &size); + if (rv != ERROR_BUFFER_OVERFLOW) + goto end; + + adapters = (PIP_ADAPTER_ADDRESSES)malloc(size); + if (!adapters) + goto end; + + rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, + NULL, adapters, &size); + if (rv != NO_ERROR) { + free(adapters); + goto end; + } + + for (a = adapters; a != NULL; a = a->Next) { + for (u = a->FirstUnicastAddress; u; u = u->Next) { + struct sockaddr_in *ua = + (struct sockaddr_in *)u->Address.lpSockaddr; + if (ua->sin_addr.s_addr == src.sin_addr.s_addr) { + info->ifindex = (uint32_t)a->IfIndex; + /* FriendlyName — WCHAR, приводим к UTF-8 */ + WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, + info->name, IF_NAMESIZE, + NULL, NULL); + ret = 0; + goto win_done; + } + } + } +win_done: + free(adapters); + } + +#else /* Linux / BSD / macOS */ + { + struct ifaddrs *ifap, *ifa; + + if (getifaddrs(&ifap) != 0) + goto end; + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) + continue; + + struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; + if (addr->sin_addr.s_addr == src.sin_addr.s_addr) { + strncpy(info->name, ifa->ifa_name, IF_NAMESIZE - 1); + info->ifindex = if_nametoindex(ifa->ifa_name); + ret = 0; + break; + } + } + freeifaddrs(ifap); + } +#endif + +end: + socket_close(sock); + return ret; +} + +/* ===================== Пример использования ===================== +int main(int argc, char **argv) +{ + iface_info_t info; + const char *dst = (argc > 1) ? argv[1] : "8.8.8.8"; + int port = (argc > 2) ? atoi(argv[2]) : 53; + + if (get_outgoing_iface(dst, port, &info) == 0) { + printf("Destination : %s:%d\n", dst, port); + printf("Source IP : %s\n", info.ip); + printf("Iface name : %s\n", info.name); + printf("Iface index : %u\n", info.ifindex); + return 0; + } + + fprintf(stderr, "Failed to determine outgoing interface (errno=%d)\n", +#ifdef _WIN32 + (int)WSAGetLastError() +#else + errno +#endif + ); + return 1; +} +*/ \ No newline at end of file diff --git a/src/etcp.c b/src/etcp.c index 308cdd86..8fe3bcb9 100644 --- a/src/etcp.c +++ b/src/etcp.c @@ -1021,7 +1021,11 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d // Update connection statistics - etcp->unacked_bytes -= acked_pkt->ll.len; + if (etcp->unacked_bytes >= acked_pkt->ll.len) etcp->unacked_bytes -= acked_pkt->ll.len; + else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] unacked_bytes underflow prevented: %u < %u", etcp->log_name, etcp->unacked_bytes, acked_pkt->ll.len); + etcp->unacked_bytes = 0; + } etcp->bytes_sent_total += acked_pkt->ll.len; etcp->ack_packets_count++; @@ -1063,6 +1067,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { // Process sections as per protocol.txt switch (type) { case ETCP_SECTION_ACK: { + if (len < 2) { len = 0; break; } int elm_cnt=data[1]; uint32_t till=data[2] | (data[3]<<8) | (data[4]<<16) | (data[5]<<24); uint16_t rx_dup_count_16 = data[6] | (data[7]<<8); @@ -1070,6 +1075,11 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { int16_t diff = (int16_t)(rx_dup_count_16 - old_tx_dup_16); etcp->tx_dup_count += diff; int ack_section_len = 8 + elm_cnt * 8; + if (ack_section_len > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] ACK section too large: elm_cnt=%d len=%d", etcp->log_name, elm_cnt, len); + len = 0; + break; + } data+=ack_section_len; len-=ack_section_len; for (int i=0; iinstance->ack_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name); + len = 0; + break; + } p->seq=seq; p->pkt_timestamp=pkt->timestamp; p->recv_timestamp=get_current_timestamp(); @@ -1218,6 +1234,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { rx_pkt->ll.len = pkt_len; rx_pkt->ll.dgram_pool = etcp->instance->data_pool; rx_pkt->ll.memlen = etcp->instance->data_pool->object_size; + if (pkt_len > (int)etcp->instance->data_pool->object_size) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] payload too large for data_pool: %u > %zu", etcp->log_name, pkt_len, etcp->instance->data_pool->object_size); + memory_pool_free(etcp->instance->data_pool, payload_data); + queue_entry_free(&rx_pkt->ll); + len = 0; + break; + } // Copy the actual payload data memcpy(payload_data, data + 5, pkt_len); queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt, 0, 4); diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 71c10f7b..eec678db 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -108,6 +108,11 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) { int to_add=s-offset-UDP_HDR_SIZE - UDP_SC_HDR_SIZE; if (to_add<0) to_add=0; + int max_data = PACKET_DATA_SIZE - (int)sizeof(struct ETCP_DGRAM); + if (offset + to_add + SC_PUBKEY_ENC_SIZE > max_data) { + to_add = max_data - offset - SC_PUBKEY_ENC_SIZE; + if (to_add<0) to_add=0; + } for (int i=0; idata[offset++]=rand();// fill pad // padding end @@ -457,6 +462,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S int fib = server->fib; uint8_t type = server->type; int mtu = server->mtu ? server->mtu : instance->config->global.mtu; + if (mtu == 0 || mtu > PACKET_DATA_MAX_MTU) mtu = PACKET_DATA_MAX_MTU; int loss_rate = server->loss_rate; uint8_t only_local = server->only_local; char* name = server->name; @@ -696,6 +702,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn link->is_server = is_server; int mtu = conn->mtu; if (mtu == 0) mtu = 1500; + if (mtu > PACKET_DATA_MAX_MTU) mtu = PACKET_DATA_MAX_MTU; link->mtu_local = mtu; link->mtu = mtu; link->initialized = 0; @@ -1234,6 +1241,11 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { errorcode=7; goto ec_fr; } + if (pkt_len<12) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "decrypted packet too short for INIT header, pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str); + errorcode=7; + goto ec_fr; + } pkt->data_len=pkt_len-3; pkt->noencrypt_len=0; @@ -1252,6 +1264,11 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { } *ack_hdr=(void*)&pkt->data[0]; uint64_t peer_id = be64toh(*(uint64_t*)ack_hdr->id); if (ack_hdr->code == ETCP_PING) { + if (pkt_len < 22) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PING too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str); + errorcode=7; + goto ec_fr; + } uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9)); uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 17)); const uint8_t* udata = (ulen > 0) ? (pkt->data + 19) : NULL; @@ -1289,6 +1306,11 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { return; } if (ack_hdr->code == ETCP_PONG) { + if (pkt_len < 20) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str); + errorcode=7; + goto ec_fr; + } uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9)); uint16_t ulen = 0; const uint8_t* udata = NULL; @@ -1466,6 +1488,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { } link->mtu_remote = (ack_hdr->mtu[0] << 8) | ack_hdr->mtu[1]; + if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU; link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote; struct { @@ -1526,6 +1549,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE; if (to_add<0) to_add=0; + if (xoffset + to_add > PACKET_DATA_SIZE) { to_add = PACKET_DATA_SIZE - xoffset; if (to_add<0) to_add=0; } for (int i=0; idata[xoffset++]=rand();// fill pad // padding end @@ -1605,6 +1629,7 @@ process_decrypted: return; } link->mtu_remote = (pkt->data[offset++] << 8) | pkt->data[offset++]; + if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU; link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote; link->remote_link_id = pkt->data[offset++]; link->remote_socket_id = pkt->data[offset++]; diff --git a/src/etcp_connections.h b/src/etcp_connections.h index d184cb31..3cf9a8d6 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -15,6 +15,7 @@ #define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки #define PACKET_DATA_SIZE 1600//1536 +#define PACKET_DATA_MAX_MTU 1600 // Типы кодограмм протокола #define ETCP_INIT_REQUEST 0x02 diff --git a/src/etcp_loadbalancer.c b/src/etcp_loadbalancer.c index 299a5235..28e370f1 100644 --- a/src/etcp_loadbalancer.c +++ b/src/etcp_loadbalancer.c @@ -114,8 +114,7 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) { struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL; if (!etcp) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] no ETCP_CONN associated with dgram", etcp->log_name); - memory_pool_free(etcp->instance->pkt_pool, dgram); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no ETCP_CONN associated with dgram (link=%p)", (void*)dgram->link); return; } diff --git a/src/route_p2pconn.txt b/src/route_p2pconn.txt new file mode 100644 index 00000000..98ccf871 --- /dev/null +++ b/src/route_p2pconn.txt @@ -0,0 +1,11 @@ +Установка прямого подключения между узлами + +1. узел собирает адреса - кандидаты для подключения: +список пар адрес-порт: + - адреса и порты из конфига сокетов (если не 0.0.0.0) + - адреса и порты из сокетов (nat) где nat = EIM +список ретрансляторов узлов-кандидатов: + - rtt, nodeid, адрес-порт + + +пары ip:port передаются на peer в команде "direct connection request" ROUTE_SUBCMD_CONN_REQ 0x0B diff --git a/utun.md b/utun.md new file mode 100644 index 00000000..ae7847bc --- /dev/null +++ b/utun.md @@ -0,0 +1,218 @@ +# uTun — защищённый VPN-туннель поверх UDP + +uTun создаёт виртуальный TUN-интерфейс и маршрутизирует IP-трафик через зашифрованные UDP-каналы по протоколу **ETCP** (Extended TCP) — комбинация TCP-надёжности + QUIC-мультиплексирования + multi-link балансировки. + +## Возможности + +| Возможность | Описание | +|---|---| +| **Шифрование** | ECC (secp256r1) обмен ключами + AES-128-CCM с аутентификацией | +| **Целостность** | CRC32 внутри зашифрованного payload | +| **Multi-link** | Одно подключение через несколько UDP-каналов одновременно | +| **Балансировка** | Выбор канала с минимальным inflight + round-robin при равенстве | +| **Bandwidth shaping** | Ограничение полосы на каждом канале с автооценкой | +| **Inflight control** | Динамическое ограничение байт в полёте для предотвращения перегрузки | +| **Retransmission** | Адаптивный таймаут: RTT_avg × k₁ + jitter × k₂ | +| **Keepalive** | Двусторонний, с настраиваемым интервалом и таймаутом | +| **Фрагментация/сборка** | Normalizer: упаковка мелких пакетов вместе, разбиение крупных | +| **Маршрутизация** | Локальная (через TUN + системные маршруты) и распределённая (BGP-обмен) | +| **NAT-детекция** | Определение типа NAT (open/restricted) через ping-пробы | +| **Файрвол** | Белый список IP:port для трафика через туннель | +| **Control server** | TCP-сервер для мониторинга метрик в реальном времени (GUI-клиент) | +| **Горячая перезагрузка** | SIGHUP — выборочное обновление конфига (сравнение сокетов/клиентов/линков) | +| **Авто-ключи** | При первом запуске генерирует ECC-пару и node_id, записывает в конфиг | +| **Демонизация** | fork/setsid на Linux; foreground-режим `-f` для отладки | +| **Кроссплатформенность** | Linux (epoll), Windows (wintun), FreeBSD | +| **Эмуляция потерь** | `loss_rate=N` в конфиге сокета — для тестирования | + +## Архитектура (кратко) + +``` +Приложение/ОС + ↓ IP-пакеты +TUN interface (tun_if) + ↓ queue +Routing (routing.c) — поиск маршрута + ↓ (если удалённый) +Normalizer (pkt_normalizer.c) — фрагментация/упаковка + ↓ +ETCP (etcp.c) — seq/ack, retransmit, reassembly + ↓ +LoadBalancer (etcp_loadbalancer.c) — выбор канала + ↓ +Encrypt + UDP send (etcp_connections.c) + ↓ +Сеть (UDP) +``` + +## Конфигурация (INI-формат) + +### CLI-запуск + +``` +utun -c utun.conf [-p /var/run/utun.pid] [-l utun.log] [-f] [-d "etcp:debug"] +``` + +- `-c` — путь к конфигу (по умолчанию `utun.conf`) +- `-p` — PID-файл +- `-l` — лог-файл (по умолчанию `utun.log`) +- `-f` — foreground (не демонизироваться) +- `-d` — отладочный конфиг на лету, формат: `"категория:уровень,..."` + +### Секции конфига + +#### `[global]` — основные параметры + +| Ключ | Значение | По умолчанию | +|---|---|---| +| `my_node_name` | Имя узла (до 15 символов) | — | +| `my_node_id` | 64-битный ID узла (hex, 16 символов) | авто | +| `my_private_key` | Приватный ключ ECC (64 hex символа) | авто | +| `my_public_key` | Публичный ключ ECC (128 hex символов) | авто | +| `tun_ifname` | Имя TUN-интерфейса | `tun0` | +| `tun_ip` | IP-адрес TUN-интерфейса | — | +| `mtu` | MTU для всех подключений | `1500` | +| `keepalive_timeout` | Таймаут keepalive (мс) | `2000` | +| `keepalive_interval` | Интервал keepalive (мс) | `200` | +| `log_file` | Путь к лог-файлу | — | +| `debug_level` | Уровень отладки: `error`, `warn`, `info`, `debug`, `trace` | `error` | +| `tun_test_mode` | 1 — не создавать реальный TUN (тесты) | `0` | +| `enable_timestamp` | Метки времени в логах | `1` | +| `enable_function_names` | Имена функций в логах | `1` | +| `enable_file_lines` | Файл:строка в логах | `0` | +| `enable_colors` | ANSI-цвета в терминале | `0` | + +#### `[debug]` — по-категорийные уровни (переопределяют `debug_level`) + +Доступные категории: +`uasync`, `ll_queue`, `connection`, `etcp`, `crypto`, `memory`, `timing`, `config`, `tun`, `routing`, `timers`, `normalizer`, `bgp`, `socket`, `general`, `control`, `dump`, `traffic` + +Пример: +```ini +[debug] +etcp=info +routing=trace +bgp=trace +crypto=error +``` + +#### `[server: ИМЯ]` — локальные UDP-сокеты (обязательно хотя бы один) + +| Ключ | Значение | По умолчанию | +|---|---|---| +| `addr` | `IP:port` для bind | **обязателен** | +| `type` | `public`, `nat`, `private` | `unknown` | +| `mtu` | MTU для этого сокета | из `[global]` | +| `loss_rate` | % потерь пакетов (0–100) | `0` | +| `so_mark` | Linux SO_MARK для policy routing | — | +| `netif` | Привязка к интерфейсу (по имени) | — | +| `fib` | FreeBSD FIB (таблица маршрутизации) | `-1` (не задан) | +| `only_local` | 1 — только локальные соединения, без forwarding | `0` | + +Пример: +```ini +[server: lan1] +addr=192.168.29.117:1333 +type=public +``` + +#### `[client: ИМЯ]` — исходящие подключения к пиру + +| Ключ | Значение | По умолчанию | +|---|---|---| +| `link` | `сервер:удалённый_IP:порт` (можно несколько) | **обязателен** | +| `peer_public_key` | Публичный ключ пира (128 hex) | **обязателен** | +| `keepalive` | 1 — включить keepalive | `1` | + +Пример: +```ini +[client: aeza] +link=lan1:85.192.42.96:1333 +peer_public_key=04c1cae041a8e6bfba5245f6669c73f0793d7f9929300a2ba2e123ca55260d6e47... +``` + +> **Важно:** в серверном конфиге секции `[client]` нет. В клиентском — есть свои ключи + `peer_public_key` каждого сервера в `[client]`. + +#### `[routing]` — маршруты + +| Ключ | Значение | +|---|---| +| `route_subnet` | Подсеть, доступная через этот TUN (добавляется в системную таблицу маршрутизации). Можно несколько | +| `my_subnet` | Своя подсеть, анонсируемая через BGP | + +Пример: +```ini +[routing] +route_subnet=10.23.0.0/16 +route_subnet=10.24.0.0/16 +my_subnet=10.23.1.0/24 +``` + +#### `[firewall]` — контроль доступа + +| Ключ | Значение | +|---|---| +| `allow` | `all` — разрешить всё. Или `IP` / `IP:port` (можно несколько) | + +По умолчанию: deny all (если нет allow=all). + +Пример: +```ini +[firewall] +allow=192.168.1.100 +allow=10.0.0.0/8:443 +``` + +#### `[control]` — сервер мониторинга + +| Ключ | Значение | По умолчанию | +|---|---|---| +| `control_ip` | IP для прослушивания | `127.0.0.1` | +| `control_port` | Порт | — | +| `control_allow` | `IP/mask` (можно несколько) | deny all | + +Пример: +```ini +[control] +control_ip=192.168.29.117 +control_port=9090 +control_allow=192.168.0.0/16 +``` + +#### `[allowed_keys]` — белый список публичных ключей пиров + +| Ключ | Значение | По умолчанию | +|---|---|---| +| `allow_all` | 1 — разрешить все ключи | — | +| `key` | Публичный ключ (128 hex, можно несколько) | — | + +Поведение: +- Секция отсутствует: разрешить все (совместимость) +- `allow_all=0` + нет ключей: запретить все +- `allow_all=0` + ключи: только перечисленные ключи + +Пример: +```ini +[allowed_keys] +key=c6c8a8a9616ffa6cf44d4757e2bc9f7bd78a1d3cbbe75cffaf3cab8885ad48e7677b... +key=04c1cae041a8e6bfba5245f6669c73f0793d7f9929300a2ba2e123ca55260d6e4748... +``` + +### Пример минимального конфига + +```ini +[global] +tun_ip=10.23.1.1 +my_node_name=vmL1 +my_node_id=5f75c7445af88e1f +my_private_key=b0e8b68679db468979906894fe36239dbda910e06435361d696c7f5a8e2009c4 +my_public_key=c6c8a8a9616ffa6cf44d4757e2bc9f7bd78a1d3cbbe75cffaf3cab8885ad48e7677b... + +[server: lan1] +addr=192.168.29.117:1333 +type=public + +[routing] +route_subnet=10.23.0.0/16 +my_subnet=10.23.1.0/24 +```