From 9517454ee6207398d79d9d2d8c9544b0009eab4c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 26 Mar 2026 22:42:22 +0300 Subject: [PATCH] firewall + random init pkt size --- src/Makefile.am | 3 +- src/config_parser.c | 90 ++++++++++++++++++++++++++++++++++++++- src/config_parser.h | 11 +++++ src/etcp_connections.c | 30 ++++++++++++- src/etcp_connections.h | 6 +++ src/firewall.c | 95 ++++++++++++++++++++++++++++++++++++++++++ src/firewall.h | 27 ++++++++++++ src/utun.c | 3 ++ src/utun_instance.c | 12 ++++++ src/utun_instance.h | 4 ++ tests/Makefile.am | 1 + utun.conf.sample | 8 ++++ 12 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 src/firewall.c create mode 100644 src/firewall.h diff --git a/src/Makefile.am b/src/Makefile.am index 9ca4662..5122bee 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,8 @@ utun_CORE_SOURCES = \ pkt_normalizer.c \ packet_dump.c \ etcp_api.c \ - control_server.c + control_server.c \ + firewall.c # Platform-specific TUN libs (Windows only) utun_TUN_LIBS = @TUN_LIBS@ diff --git a/src/config_parser.c b/src/config_parser.c index b59698a..c8b0a6d 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -21,6 +21,7 @@ /* Forward declaration for new functions */ static int assign_string(char *dest, size_t dest_size, const char *src); +static int parse_firewall_rule(const char *rule_str, struct global_config *global); typedef enum { SECTION_UNKNOWN, @@ -28,7 +29,8 @@ typedef enum { SECTION_SERVER, SECTION_CLIENT, SECTION_ROUTING, - SECTION_DEBUG + SECTION_DEBUG, + SECTION_FIREWALL } section_type_t; static char* trim(char *str) { @@ -203,6 +205,59 @@ static int add_route_entry(struct CFG_ROUTE_ENTRY **list, const char *subnet_str return 0; } +static int parse_firewall_rule(const char *rule_str, struct global_config *global) { + if (!rule_str || !global) return -1; + + char rule_copy[64]; + strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1); + rule_copy[sizeof(rule_copy) - 1] = '\0'; + trim(rule_copy); + + if (strcasecmp(rule_copy, "all") == 0) { + global->firewall_bypass_all = 1; + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall: bypass all enabled"); + return 0; + } + + // Parse IP:port or IP + char *port_str = strchr(rule_copy, ':'); + uint16_t port = 0; + if (port_str) { + *port_str = '\0'; + port = (uint16_t)atoi(port_str + 1); + } + + // Parse IP to uint32_t (host order) + struct in_addr addr; + if (inet_pton(AF_INET, rule_copy, &addr) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_firewall_rule: invalid IP: %s", rule_str); + return -1; + } + + uint32_t ip = ntohl(addr.s_addr); // convert to host order for sorting + + // Reallocate array if needed + if ((global->firewall_rule_count % INITIAL_ARRAY_CAPACITY) == 0) { + int new_cap = global->firewall_rule_count + INITIAL_ARRAY_CAPACITY; + struct CFG_FIREWALL_RULE *new_rules = u_realloc(global->firewall_rules, + new_cap * sizeof(struct CFG_FIREWALL_RULE)); + if (!new_rules) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc firewall rules"); + return -1; + } + global->firewall_rules = new_rules; + } + + struct CFG_FIREWALL_RULE *rule = &global->firewall_rules[global->firewall_rule_count]; + rule->ip = ip; + rule->port = port; + rule->bypass = 0; + global->firewall_rule_count++; + + DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Added firewall rule: %s:%d", rule_str, port); + return 0; +} + static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) { struct CFG_SERVER *srv = servers; while (srv) { @@ -392,6 +447,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t if (strcasecmp(section, "global") == 0) return SECTION_GLOBAL; if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG; + if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL; char *colon = strchr(section, ':'); if (!colon) return SECTION_UNKNOWN; @@ -418,6 +474,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) // Set default values cfg->global.keepalive_timeout = 2000; // Default 2 seconds + cfg->global.firewall_rules = NULL; + cfg->global.firewall_rule_count = 0; + cfg->global.firewall_bypass_all = 0; section_type_t cur_section = SECTION_UNKNOWN; struct CFG_SERVER *cur_server = NULL; @@ -508,6 +567,13 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) cfg->global.debug_levels.count++; } break; + case SECTION_FIREWALL: + if (strcmp(key, "allow") == 0) { + if (parse_firewall_rule(value, &cfg->global) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value); + } + } + break; default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break; @@ -580,6 +646,9 @@ void free_config(struct utun_config *config) { free_route_entries(config->route_subnets); free_route_entries(config->my_subnets); + // Free firewall rules + u_free(config->global.firewall_rules); + u_free(config); } @@ -644,6 +713,25 @@ void print_config(const struct utun_config *cfg) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask); my = my->next; } + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall:"); + if (cfg->global.firewall_bypass_all) { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " bypass_all=1"); + } else if (cfg->global.firewall_rule_count > 0) { + for (int i = 0; i < cfg->global.firewall_rule_count; i++) { + struct CFG_FIREWALL_RULE *r = &cfg->global.firewall_rules[i]; + char ip_str[16]; + struct in_addr ia = { .s_addr = htonl(r->ip) }; + inet_ntop(AF_INET, &ia, ip_str, sizeof(ip_str)); + if (r->port == 0) { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s", ip_str); + } else { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s:%d", ip_str, r->port); + } + } + } else { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules"); + } } int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) { diff --git a/src/config_parser.h b/src/config_parser.h index e0c8a6c..617219d 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -59,6 +59,12 @@ struct CFG_ROUTE_ENTRY { uint8_t netmask; }; +struct CFG_FIREWALL_RULE { + uint32_t ip; // IPv4 in host byte order for sorting + uint16_t port; // 0 means any port + uint8_t bypass; // 1 for allow=all (bypass all checks) +}; + struct global_config { char my_private_key_hex[MAX_KEY_LEN]; char my_public_key_hex[MAX_KEY_LEN]; @@ -88,6 +94,11 @@ struct global_config { int tun_test_mode; // test mode: 1 = don't open real TUN, queues only int keepalive_timeout; // keepalive timeout in ms (default: 2000) int keepalive_interval; // keepalive interval in ms (default: 200) + + // Firewall configuration + struct CFG_FIREWALL_RULE *firewall_rules; + int firewall_rule_count; + int firewall_bypass_all; }; struct utun_config { diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 6ae2c67..27d12be 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -4,6 +4,7 @@ #ifndef _WIN32 #include #endif +#include #include #include #include "utun_instance.h" @@ -84,6 +85,16 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) { dgram->data[offset++] = link->local_link_id; + // padding + int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize; + if (s > link->mtu - dgram->noencrypt_len) s = link->mtu - dgram->noencrypt_len; + + int to_add=s-offset-UDP_HDR_SIZE - UDP_SC_HDR_SIZE; + if (to_add<0) to_add=0; + + for (int i=0; idata[offset++]=rand();// fill pad + // padding end + uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE]; random_bytes(salt, sizeof(salt)); memcpy(dgram->data + offset, salt, SC_PUBKEY_ENC_SALT_SIZE); @@ -95,6 +106,7 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) { memcpy(dgram->data + offset, obfuscated_pubkey, SC_PUBKEY_SIZE); offset += SC_PUBKEY_SIZE; + dgram->data_len = offset; DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%016llx, retry=%d", (unsigned long long)node_id, link->init_retry_count); @@ -568,6 +580,9 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn link->init_timeout = 0; link->init_retry_count = 0; link->link_status = 0; // down initially + link->handshake_minsize = 100; + link->handshake_maxsize = mtu;// 28 = udp header size + // Initialize keepalive timeout from global config if (etcp->instance && etcp->instance->config) { @@ -865,7 +880,7 @@ static void etcp_connections_read_callback_socket(socket_t sock, void* arg) { // Try INIT decryption (for incoming connection requests) // This handles: no link found, or link without session, or normal decrypt failed - if (recv_len <= SC_PUBKEY_ENC_SIZE) { + if (recv_len <= SC_PUBKEY_ENC_SIZE + UDP_SC_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet too small for init, size=%zd", recv_len); errorcode=1; goto ec_fr; @@ -1036,11 +1051,22 @@ static void etcp_connections_read_callback_socket(socket_t sock, void* arg) { memset(ack_repl_hdr->peer_ipv4, 0, 4); memset(ack_repl_hdr->peer_port, 0, 2); } - pkt->data_len=sizeof(*ack_repl_hdr); pkt->noencrypt_len=0; pkt->link=link; link->recv_keepalive = 1; + int xoffset=sizeof(*ack_repl_hdr); + // padding + int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize; + if (s > link->mtu) s = link->mtu; + + int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE; + if (to_add<0) to_add=0; + + for (int i=0; idata[xoffset++]=rand();// fill pad + // padding end + pkt->data_len=xoffset; + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE"); etcp_encrypt_send(pkt); diff --git a/src/etcp_connections.h b/src/etcp_connections.h index 44b3f3f..b8f9a30 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -8,6 +8,10 @@ #include "../lib/socket_compat.h" #include +#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт] +#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr + + #define PACKET_DATA_SIZE 1600//1536 // Типы кодограмм протокола @@ -151,6 +155,8 @@ struct ETCP_LINK { uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика uint32_t keepalive_sent_count; // Счётчик отправленных keepalive uint32_t keepalive_recv_count; // Счётчик полученных keepalive + uint16_t handshake_minsize; // минимальный размер udp при handshake + uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом) }; // INITIALIZATION (создаёт listen-сокеты и подключения из конфига) diff --git a/src/firewall.c b/src/firewall.c new file mode 100644 index 0000000..62d2f15 --- /dev/null +++ b/src/firewall.c @@ -0,0 +1,95 @@ +// firewall.c - Firewall module for utun +#define _POSIX_C_SOURCE 200809L +#include "firewall.h" +#include +#include +#include "../lib/debug_config.h" +#include "../lib/mem.h" + +static int compare_rules(const void *a, const void *b) { + const struct CFG_FIREWALL_RULE *ra = (const struct CFG_FIREWALL_RULE *)a; + const struct CFG_FIREWALL_RULE *rb = (const struct CFG_FIREWALL_RULE *)b; + if (ra->ip != rb->ip) { + return (ra->ip > rb->ip) ? 1 : -1; + } + return (ra->port > rb->port) ? 1 : -1; +} + +int fw_init(struct firewall_ctx *ctx) { + if (!ctx) return -1; + memset(ctx, 0, sizeof(struct firewall_ctx)); + return 0; +} + +int fw_load_rules(struct firewall_ctx *ctx, const struct global_config *cfg) { + if (!ctx || !cfg) return -1; + + fw_free(ctx); + fw_init(ctx); + + ctx->bypass_all = cfg->firewall_bypass_all; + + if (cfg->firewall_rule_count > 0) { + ctx->rules = u_calloc(cfg->firewall_rule_count, sizeof(struct CFG_FIREWALL_RULE)); + if (!ctx->rules) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "fw_load_rules: failed to allocate rules"); + return -1; + } + memcpy(ctx->rules, cfg->firewall_rules, + cfg->firewall_rule_count * sizeof(struct CFG_FIREWALL_RULE)); + ctx->count = cfg->firewall_rule_count; + + // Sort rules by IP (and port) + qsort(ctx->rules, ctx->count, sizeof(struct CFG_FIREWALL_RULE), compare_rules); + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Loaded %d firewall rules (sorted)", ctx->count); + } + + if (ctx->bypass_all) { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall bypass_all mode enabled"); + } + + return 0; +} + +int fw_check(const struct firewall_ctx *ctx, uint32_t ip, uint16_t port) { + if (!ctx) return 0; + if (ctx->bypass_all) return 1; + + // Binary search for IP + int low = 0; + int high = ctx->count - 1; + while (low <= high) { + int mid = (low + high) / 2; + if (ctx->rules[mid].ip == ip) { + // Found matching IP, check port + int i = mid; + // Check exact match or any-port rule + while (i >= 0 && ctx->rules[i].ip == ip) { + if (ctx->rules[i].port == 0 || ctx->rules[i].port == port) { + return 1; + } + i--; + } + i = mid + 1; + while (i < ctx->count && ctx->rules[i].ip == ip) { + if (ctx->rules[i].port == 0 || ctx->rules[i].port == port) { + return 1; + } + i++; + } + return 0; + } else if (ctx->rules[mid].ip < ip) { + low = mid + 1; + } else { + high = mid - 1; + } + } + return 0; +} + +void fw_free(struct firewall_ctx *ctx) { + if (!ctx) return; + u_free(ctx->rules); + memset(ctx, 0, sizeof(struct firewall_ctx)); +} diff --git a/src/firewall.h b/src/firewall.h new file mode 100644 index 0000000..ce11c98 --- /dev/null +++ b/src/firewall.h @@ -0,0 +1,27 @@ +// firewall.h - Firewall rules for utun +#ifndef FIREWALL_H +#define FIREWALL_H + +#include +#include "config_parser.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct firewall_ctx { + struct CFG_FIREWALL_RULE *rules; + int count; + int bypass_all; +}; + +int fw_init(struct firewall_ctx *ctx); +int fw_load_rules(struct firewall_ctx *ctx, const struct global_config *cfg); +int fw_check(const struct firewall_ctx *ctx, uint32_t ip, uint16_t port); +void fw_free(struct firewall_ctx *ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/utun.c b/src/utun.c index b118f4e..2c19bec 100644 --- a/src/utun.c +++ b/src/utun.c @@ -11,6 +11,8 @@ #include "debug_config.h" #include #include +#include +#include #include #include #include @@ -319,6 +321,7 @@ int main(int argc, char *argv[]) { } #endif + srand(time(NULL)); // Create uasync instance struct UASYNC* ua = uasync_create(); main_ua=ua; diff --git a/src/utun_instance.c b/src/utun_instance.c index e920888..f62d94d 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -117,6 +117,15 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); } + // Initialize firewall + fw_init(&instance->fw); + if (fw_load_rules(&instance->fw, &config->global) != 0) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Failed to load firewall rules"); + } else { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall initialized: %d rules, bypass_all=%d", + instance->fw.count, instance->fw.bypass_all); + } + return 0; } @@ -264,6 +273,9 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { instance->bgp = NULL; } + // Cleanup firewall + fw_free(&instance->fw); + // Cleanup config if (instance->config) { DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); diff --git a/src/utun_instance.h b/src/utun_instance.h index 4a72182..28c1753 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -8,6 +8,7 @@ #include "secure_channel.h" #include "etcp_api.h" #include "config_parser.h" +#include "firewall.h" // Forward declarations struct utun_config; @@ -70,6 +71,9 @@ struct UTUN_INSTANCE { // Control server for monitoring struct control_server* control_srv; + + // Firewall + struct firewall_ctx fw; }; // Functions diff --git a/tests/Makefile.am b/tests/Makefile.am index 491d927..35407f4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -84,6 +84,7 @@ ETCP_FULL_OBJS = \ $(top_builddir)/src/utun-tun_if.o \ $(top_builddir)/src/utun-tun_route.o \ $(top_builddir)/src/utun-packet_dump.o \ + $(top_builddir)/src/utun-firewall.o \ $(TUN_PLATFORM_OBJ) \ $(top_builddir)/src/utun-utun_instance.o \ $(ETCP_CORE_OBJS) diff --git a/utun.conf.sample b/utun.conf.sample index 48d47ff..5bd8c82 100644 --- a/utun.conf.sample +++ b/utun.conf.sample @@ -30,3 +30,11 @@ addr=192.168.29.117:1333 #so_mark=100 #netif=eth0 type=public # public / nat / private + +[firewall] +#ограничиваем подключения к собственным ресурсам. по умолчанию всё запрещено. разрешаем доступ директивами: +# Firewall rules: allow=all, allow=IP or allow=IP:port +allow=all +#allow=192.168.1.100 +#allow=10.0.0.50:443 +#allow=8.8.8.8