From 5de0eb51154850b5e95e958d848396f57bc0a5f6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 19:49:32 +0200 Subject: [PATCH] Filter both directions of the main TUN and remove obsolete firewall guide --- src/firewall_doc.md | 28 ------------------ src/routing_layer/routing.c | 59 ++++++++++++++++++++++++------------- utun.conf.sample | 6 ++-- 3 files changed, 42 insertions(+), 51 deletions(-) delete mode 100644 src/firewall_doc.md diff --git a/src/firewall_doc.md b/src/firewall_doc.md deleted file mode 100644 index 8841331f..00000000 --- a/src/firewall_doc.md +++ /dev/null @@ -1,28 +0,0 @@ -# Firewall rule engine - -## 1. Назначение -Фильтр пакетов по IP/порту для входящего/исходящего трафика. Правила загружаются из конфига, сортируются по IP и порту, затем проверка идёт бинарным поиском. Используется для разрешения/запрета прохождения пакетов через туннель. - -## 2. Как пользоваться -```c -struct firewall_ctx fw; -fw_init(&fw); -fw_load_rules(&fw, &global_cfg); -if (fw_check(&fw, ip_host, port)) { /* пакет разрешён */ } -fw_free(&fw); -``` -Правила задаются в конфиге в секции `[firewall]`: -- `rule = ip port` — разрешить IP:port (port=0 — любой порт) -- `bypass_all = 1` — пропускать всё независимо от правил - -Сортировка — по IP, затем по порту. При проверке: бинарный поиск по IP, затем линейный скан соседних записей с тем же IP для сверки порта. Правило с `port=0` означает любой порт для этого IP. - -## 3. API - -| Функция/Структура | Описание | -|---|---| -| `struct firewall_ctx` | Набор правил (`rules`), их количество (`count`), флаг `bypass_all` | -| `fw_init(ctx)` | Обнуление контекста | -| `fw_load_rules(ctx, cfg)` | Копирование правил из конфига, сортировка qsort, установка bypass_all | -| `fw_check(ctx, ip, port)` | Возвращает 1 если пакет разрешён (bypass_all=1 или IP+port есть в правилах), 0 — запрещён | -| `fw_free(ctx)` | Освобождение памяти, обнуление полей | diff --git a/src/routing_layer/routing.c b/src/routing_layer/routing.c index 190c9f97..eb63bf4f 100644 --- a/src/routing_layer/routing.c +++ b/src/routing_layer/routing.c @@ -56,31 +56,31 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_pkt: invalid arguments: instance=%p entry=%p entry->len=%zu", (void*)instance, (void*)entry, entry ? entry->len : 0); if (entry) { - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); } return; } if (!instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_pkt: no route table"); - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); return; } if (!instance->tun) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_pkt: no TUN interface"); - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); return; } // Check packet data (skip cmd byte) if (!entry->dgram || entry->len < 2) { DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: packet too small: len=%zu, expected >=2", entry->len); - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); return; } @@ -92,18 +92,17 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t if (ip_len > TUN_MAX_PACKET_SIZE) { DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: packet too large: len=%zu max=%d, dropping", ip_len, TUN_MAX_PACKET_SIZE); - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); return; } // Extract destination IP uint32_t dst_ip = extract_dst_ip(ip_data, ip_len); if (dst_ip == 0) { -// DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to extract dst IP: len=%zu first_byte=0x%02x, dropping", -// ip_len, ip_data ? ip_data[0] : 0); - queue_entry_free(entry); + DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: invalid IPv4 destination, len=%zu first_byte=0x%02x", ip_len, ip_data[0]); queue_dgram_free(entry); + queue_entry_free(entry); return; } @@ -112,15 +111,17 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t // Multicast: 224.0.0.0 - 239.255.255.255 if (first_octet >= 224 && first_octet <= 239) { - queue_entry_free(entry); + DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "route_pkt: multicast destination dropped"); queue_dgram_free(entry); + queue_entry_free(entry); return; } // Broadcast: only 255.255.255.255 (limited broadcast) if (dst_ip == 0xFFFFFFFF) { - queue_entry_free(entry); + DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "route_pkt: broadcast destination dropped"); queue_dgram_free(entry); + queue_entry_free(entry); return; } @@ -133,8 +134,8 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t if (!route) { DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "route_pkt: no route to %s, dropping", ip_to_str(&addr, AF_INET).str); instance->dropped_packets++; - queue_entry_free(entry); queue_dgram_free(entry); + queue_entry_free(entry); return; } @@ -156,6 +157,17 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t return; } + // Вход в основной TUN: правило назначения такое же, как при выходе из TUN. + int allowed = fw_check_packet(&instance->fw, ip_data, ip_len); + DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Firewall: to TUN=%s src_node=%016llx result=%d", + instance->tun->ifname, (unsigned long long)src_node_id, allowed); + if (allowed <= 0) { + instance->dropped_packets++; + queue_dgram_free(entry); + queue_entry_free(entry); + return; + } + if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TRAFFIC)) { char* packet_str = dump_ip_packet_to_buffer(ip_data, ip_len); DEBUG_DEBUG(DEBUG_CATEGORY_TRAFFIC, "DUMP: %s", packet_str); @@ -171,8 +183,6 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t ip_to_str(&addr, AF_INET).str, put_err); instance->dropped_packets++; instance->routed_packets--; - queue_entry_free(entry); - queue_dgram_free(entry); return; } } @@ -184,8 +194,8 @@ static void routing_pkt_from_etcp_cb(struct ETCP_CONN* conn, struct ll_entry* pk DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "invalid arguments: conn=%p pkt=%p", (void*)conn, (void*)pkt); if (pkt) { - queue_entry_free(pkt); queue_dgram_free(pkt); + queue_entry_free(pkt); } return; } @@ -194,16 +204,16 @@ static void routing_pkt_from_etcp_cb(struct ETCP_CONN* conn, struct ll_entry* pk if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "connection has no instance: conn=%p pkt_len=%zu", (void*)conn, pkt->len); - queue_entry_free(pkt); queue_dgram_free(pkt); + queue_entry_free(pkt); return; } // Реальный отправитель — из [svc_id][src_node_id][dst_node_id][ip] (роутер встраивает src/dst) - if (pkt->len < ROUTER_SVC_HDR_SIZE + 1) { + if (!pkt->dgram || pkt->len < ROUTER_SVC_HDR_SIZE + 1) { DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "routing: short packet len=%u", pkt->len); - queue_entry_free(pkt); queue_dgram_free(pkt); + queue_entry_free(pkt); return; } uint64_t src_node_id; @@ -227,10 +237,17 @@ static void routing_pkt_from_tun_cb(struct ll_queue* q, void* arg) { struct ll_entry* pkt = queue_data_get(instance->tun->output_queue); if (!pkt) return; - // Route packet (unified function handles everything) - // Source is the local node (TUN interface) - uint64_t src_node_id = instance->node_id; - route_pkt(instance, pkt, src_node_id); + // Выход из основного TUN проверяется до маршрутизации/отправки в ETCP. + int allowed = -1; + if (pkt->dgram && pkt->len > 1) allowed = fw_check_packet(&instance->fw, pkt->dgram + 1, pkt->len - 1); + else DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Firewall: malformed TUN entry, len=%u", pkt->len); + DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Firewall: from TUN=%s result=%d", instance->tun->ifname, allowed); + if (allowed > 0) route_pkt(instance, pkt, instance->node_id); + else { + instance->dropped_packets++; + queue_dgram_free(pkt); + queue_entry_free(pkt); + } // Resume callback for next packet queue_resume_callback(instance->tun->output_queue); diff --git a/utun.conf.sample b/utun.conf.sample index e6d25319..26d6db96 100644 --- a/utun.conf.sample +++ b/utun.conf.sample @@ -47,8 +47,10 @@ allow_all=0 #key=04c1cae041a8e6bfba5245f6669c73f0793d7f9929300a2ba2e123ca55260d6e474480056720d94f7f0836a4a472763a74aacbc487cc7a1b2b0e066b519400fe [firewall] -#ограничиваем подключения к собственным ресурсам. по умолчанию всё запрещено. разрешаем доступ директивами: -# Firewall rules: allow=all, allow=IP or allow=IP:port +# Основной TUN: входящие и исходящие пакеты проверяются одинаково по IP/порту назначения. +# По умолчанию всё запрещено; ответы требуют своих разрешений. NAT/proxy TUN используют отдельные интерфейсы. +# allow=IP — все протоколы IP; allow=IP:port — TCP/UDP; :0 — весь IP. Подсети CIDR не поддерживаются. +# Фрагменты IPv4 требуют allow=IP или allow=all; разрешения одного порта недостаточно. allow=all #allow=192.168.1.100 #allow=10.0.0.50:443