Browse Source

firewall + random init pkt size

nodeinfo-routing-update
Evgeny 1 week ago
parent
commit
9517454ee6
  1. 3
      src/Makefile.am
  2. 90
      src/config_parser.c
  3. 11
      src/config_parser.h
  4. 30
      src/etcp_connections.c
  5. 6
      src/etcp_connections.h
  6. 95
      src/firewall.c
  7. 27
      src/firewall.h
  8. 3
      src/utun.c
  9. 12
      src/utun_instance.c
  10. 4
      src/utun_instance.h
  11. 1
      tests/Makefile.am
  12. 8
      utun.conf.sample

3
src/Makefile.am

@ -23,7 +23,8 @@ utun_CORE_SOURCES = \
pkt_normalizer.c \ pkt_normalizer.c \
packet_dump.c \ packet_dump.c \
etcp_api.c \ etcp_api.c \
control_server.c control_server.c \
firewall.c
# Platform-specific TUN libs (Windows only) # Platform-specific TUN libs (Windows only)
utun_TUN_LIBS = @TUN_LIBS@ utun_TUN_LIBS = @TUN_LIBS@

90
src/config_parser.c

@ -21,6 +21,7 @@
/* Forward declaration for new functions */ /* Forward declaration for new functions */
static int assign_string(char *dest, size_t dest_size, const char *src); 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 { typedef enum {
SECTION_UNKNOWN, SECTION_UNKNOWN,
@ -28,7 +29,8 @@ typedef enum {
SECTION_SERVER, SECTION_SERVER,
SECTION_CLIENT, SECTION_CLIENT,
SECTION_ROUTING, SECTION_ROUTING,
SECTION_DEBUG SECTION_DEBUG,
SECTION_FIREWALL
} section_type_t; } section_type_t;
static char* trim(char *str) { 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; 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) { static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) {
struct CFG_SERVER *srv = servers; struct CFG_SERVER *srv = servers;
while (srv) { 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, "global") == 0) return SECTION_GLOBAL;
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG; if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
char *colon = strchr(section, ':'); char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN; if (!colon) return SECTION_UNKNOWN;
@ -418,6 +474,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// Set default values // Set default values
cfg->global.keepalive_timeout = 2000; // Default 2 seconds 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; section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL; 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++; cfg->global.debug_levels.count++;
} }
break; 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: default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break; break;
@ -580,6 +646,9 @@ void free_config(struct utun_config *config) {
free_route_entries(config->route_subnets); free_route_entries(config->route_subnets);
free_route_entries(config->my_subnets); free_route_entries(config->my_subnets);
// Free firewall rules
u_free(config->global.firewall_rules);
u_free(config); 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); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask);
my = my->next; 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) { int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {

11
src/config_parser.h

@ -59,6 +59,12 @@ struct CFG_ROUTE_ENTRY {
uint8_t netmask; 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 { struct global_config {
char my_private_key_hex[MAX_KEY_LEN]; char my_private_key_hex[MAX_KEY_LEN];
char my_public_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 tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000) int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200) 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 { struct utun_config {

30
src/etcp_connections.c

@ -4,6 +4,7 @@
#ifndef _WIN32 #ifndef _WIN32
#include <net/if.h> #include <net/if.h>
#endif #endif
#include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include "utun_instance.h" #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; 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; i<to_add; i++) dgram->data[offset++]=rand();// fill pad
// padding end
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE]; uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
random_bytes(salt, sizeof(salt)); random_bytes(salt, sizeof(salt));
memcpy(dgram->data + offset, salt, SC_PUBKEY_ENC_SALT_SIZE); 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); memcpy(dgram->data + offset, obfuscated_pubkey, SC_PUBKEY_SIZE);
offset += SC_PUBKEY_SIZE; offset += SC_PUBKEY_SIZE;
dgram->data_len = offset; 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); 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_timeout = 0;
link->init_retry_count = 0; link->init_retry_count = 0;
link->link_status = 0; // down initially link->link_status = 0; // down initially
link->handshake_minsize = 100;
link->handshake_maxsize = mtu;// 28 = udp header size
// Initialize keepalive timeout from global config // Initialize keepalive timeout from global config
if (etcp->instance && etcp->instance->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) // Try INIT decryption (for incoming connection requests)
// This handles: no link found, or link without session, or normal decrypt failed // 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); DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet too small for init, size=%zd", recv_len);
errorcode=1; errorcode=1;
goto ec_fr; 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_ipv4, 0, 4);
memset(ack_repl_hdr->peer_port, 0, 2); memset(ack_repl_hdr->peer_port, 0, 2);
} }
pkt->data_len=sizeof(*ack_repl_hdr);
pkt->noencrypt_len=0; pkt->noencrypt_len=0;
pkt->link=link; pkt->link=link;
link->recv_keepalive = 1; 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; i<to_add; i++) pkt->data[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_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"); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
etcp_encrypt_send(pkt); etcp_encrypt_send(pkt);

6
src/etcp_connections.h

@ -8,6 +8,10 @@
#include "../lib/socket_compat.h" #include "../lib/socket_compat.h"
#include <stdint.h> #include <stdint.h>
#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 #define PACKET_DATA_SIZE 1600//1536
// Типы кодограмм протокола // Типы кодограмм протокола
@ -151,6 +155,8 @@ struct ETCP_LINK {
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive uint32_t keepalive_recv_count; // Счётчик полученных keepalive
uint16_t handshake_minsize; // минимальный размер udp при handshake
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
}; };
// INITIALIZATION (создаёт listen-сокеты и подключения из конфига) // INITIALIZATION (создаёт listen-сокеты и подключения из конфига)

95
src/firewall.c

@ -0,0 +1,95 @@
// firewall.c - Firewall module for utun
#define _POSIX_C_SOURCE 200809L
#include "firewall.h"
#include <stdlib.h>
#include <string.h>
#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));
}

27
src/firewall.h

@ -0,0 +1,27 @@
// firewall.h - Firewall rules for utun
#ifndef FIREWALL_H
#define FIREWALL_H
#include <stdint.h>
#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

3
src/utun.c

@ -11,6 +11,8 @@
#include "debug_config.h" #include "debug_config.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -319,6 +321,7 @@ int main(int argc, char *argv[]) {
} }
#endif #endif
srand(time(NULL));
// Create uasync instance // Create uasync instance
struct UASYNC* ua = uasync_create(); struct UASYNC* ua = uasync_create();
main_ua=ua; main_ua=ua;

12
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"); 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; return 0;
} }
@ -264,6 +273,9 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
instance->bgp = NULL; instance->bgp = NULL;
} }
// Cleanup firewall
fw_free(&instance->fw);
// Cleanup config // Cleanup config
if (instance->config) { if (instance->config) {
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration");

4
src/utun_instance.h

@ -8,6 +8,7 @@
#include "secure_channel.h" #include "secure_channel.h"
#include "etcp_api.h" #include "etcp_api.h"
#include "config_parser.h" #include "config_parser.h"
#include "firewall.h"
// Forward declarations // Forward declarations
struct utun_config; struct utun_config;
@ -70,6 +71,9 @@ struct UTUN_INSTANCE {
// Control server for monitoring // Control server for monitoring
struct control_server* control_srv; struct control_server* control_srv;
// Firewall
struct firewall_ctx fw;
}; };
// Functions // Functions

1
tests/Makefile.am

@ -84,6 +84,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-tun_if.o \ $(top_builddir)/src/utun-tun_if.o \
$(top_builddir)/src/utun-tun_route.o \ $(top_builddir)/src/utun-tun_route.o \
$(top_builddir)/src/utun-packet_dump.o \ $(top_builddir)/src/utun-packet_dump.o \
$(top_builddir)/src/utun-firewall.o \
$(TUN_PLATFORM_OBJ) \ $(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \ $(top_builddir)/src/utun-utun_instance.o \
$(ETCP_CORE_OBJS) $(ETCP_CORE_OBJS)

8
utun.conf.sample

@ -30,3 +30,11 @@ addr=192.168.29.117:1333
#so_mark=100 #so_mark=100
#netif=eth0 #netif=eth0
type=public # public / nat / private 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

Loading…
Cancel
Save