diff --git a/src/config_parser.c b/src/config_parser.c index d852250..ddaa4ec 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -30,7 +30,8 @@ typedef enum { SECTION_CLIENT, SECTION_ROUTING, SECTION_DEBUG, - SECTION_FIREWALL + SECTION_FIREWALL, + SECTION_CONTROL } section_type_t; static char* trim(char *str) { @@ -258,6 +259,51 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa return 0; } +static int parse_control_allow(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); + + uint8_t cidr = 32; + char *slash = strchr(rule_copy, '/'); + if (slash) { + *slash = '\0'; + cidr = (uint8_t)atoi(slash + 1); + if (cidr > 32) cidr = 32; + } + + struct in_addr addr; + if (inet_pton(AF_INET, rule_copy, &addr) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str); + return -1; + } + + uint32_t ip = ntohl(addr.s_addr); + uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr)); + + if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) { + int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY; + struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows, + new_cap * sizeof(struct CFG_CONTROL_ALLOW)); + if (!new_allows) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows"); + return -1; + } + global->control_allows = new_allows; + } + + struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count]; + rule->network = ip & mask; + rule->netmask = mask; + global->control_allow_count++; + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr); + return 0; +} + static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) { struct CFG_SERVER *srv = servers; while (srv) { @@ -303,29 +349,6 @@ static int parse_global(const char *key, const char *value, struct global_config global->keepalive_interval = atoi(value); return 0; } - if (strcmp(key, "control_ip") == 0) { - // Store control_ip for later processing with control_port - strncpy(global->control_ip, value, sizeof(global->control_ip) - 1); - global->control_ip[sizeof(global->control_ip) - 1] = '\0'; - return 0; - } - if (strcmp(key, "control_port") == 0) { - // Use control_ip from config, fallback to localhost if not set - char control_ip[MAX_ADDR_LEN]; - if (global->control_ip[0] != '\0') { - strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1); - } else { - strcpy(control_ip, "127.0.0.1"); - } - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%s", value); - parse_sockaddr(control_ip, port_str, &global->control_sock); - return 0; - } - if (strcmp(key, "net_debug") == 0) { - global->net_debug = atoi(value); - } - if (strcmp(key, "debug_level") == 0) { return assign_string(global->debug_level, sizeof(global->debug_level), value); } @@ -356,6 +379,30 @@ static int parse_global(const char *key, const char *value, struct global_config return 0; } +static int parse_control(const char *key, const char *value, struct global_config *global) { + if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) { + strncpy(global->control_ip, value, sizeof(global->control_ip) - 1); + global->control_ip[sizeof(global->control_ip) - 1] = '\0'; + return 0; + } + if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) { + char control_ip[MAX_ADDR_LEN]; + if (global->control_ip[0] != '\0') { + strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1); + } else { + strcpy(control_ip, "127.0.0.1"); + } + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%s", value); + parse_sockaddr(control_ip, port_str, &global->control_sock); + return 0; + } + if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) { + return parse_control_allow(value, global); + } + return 0; +} + static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) { if (strcmp(key, "addr") == 0) { return parse_address_and_port(value, &srv->ip); @@ -455,6 +502,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG; if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL; + if (strcasecmp(section, "control") == 0) return SECTION_CONTROL; char *colon = strchr(section, ':'); if (!colon) return SECTION_UNKNOWN; @@ -485,6 +533,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) cfg->global.firewall_rules = NULL; cfg->global.firewall_rule_count = 0; cfg->global.firewall_bypass_all = 0; + cfg->global.control_allows = NULL; + cfg->global.control_allow_count = 0; section_type_t cur_section = SECTION_UNKNOWN; struct CFG_SERVER *cur_server = NULL; @@ -583,6 +633,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename) } } break; + case SECTION_CONTROL: + if (parse_control(key, value, &cfg->global) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid control key '%s'", filename, line_num, key); + } + break; default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break; @@ -657,6 +712,8 @@ void free_config(struct utun_config *config) { // Free firewall rules u_free(config->global.firewall_rules); + // Free control allow rules (array allocated with realloc) + u_free(config->global.control_allows); u_free(config); } @@ -741,6 +798,8 @@ void print_config(const struct utun_config *cfg) { } else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules"); } + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count); } 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 68c7960..c606110 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -66,6 +66,11 @@ struct CFG_FIREWALL_RULE { uint8_t bypass; // 1 for allow=all (bypass all checks) }; +struct CFG_CONTROL_ALLOW { + uint32_t network; // IPv4 network in host byte order + uint32_t netmask; // netmask in host byte order (e.g. 0xffffff00 for /24) +}; + struct global_config { char name[16]; // Instance name char my_private_key_hex[MAX_KEY_LEN]; @@ -76,8 +81,7 @@ struct global_config { int mtu; struct sockaddr_storage control_sock; char control_ip[MAX_ADDR_LEN]; // Control server IP address - int net_debug; - + // Debug and logging configuration char log_file[256]; // Path to log file (empty = stdout) char debug_level[16]; // debug level: error, warn, info, debug, trace @@ -101,6 +105,10 @@ struct global_config { struct CFG_FIREWALL_RULE *firewall_rules; int firewall_rule_count; int firewall_bypass_all; + + // Control server configuration ([control] section) + struct CFG_CONTROL_ALLOW *control_allows; + int control_allow_count; }; struct utun_config { diff --git a/src/control_server.c b/src/control_server.c index be5a084..dc1cc60 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -270,6 +270,24 @@ void control_server_shutdown(struct control_server* server) { * Client Connection Handling * ============================================================================ */ +static int is_control_ip_allowed(const struct control_server* server, uint32_t client_ip) { + if (!server || !server->instance || !server->instance->config) { + DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control IP check: no config available"); + return 0; + } + const struct global_config *g = &server->instance->config->global; + if (g->control_allow_count == 0) { + DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied (no allow rules) - add control_allow=IP/mask to [control] in config"); + return 0; + } + for (int i = 0; i < g->control_allow_count; i++) { + const struct CFG_CONTROL_ALLOW *r = &g->control_allows[i]; + if ((client_ip & r->netmask) == r->network) return 1; + } + DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied from IP (not in allow list) - add control_allow=IP/mask to [control]"); + return 0; +} + static void accept_callback(socket_t fd, void* arg) { struct control_server* server = (struct control_server*)arg; @@ -318,6 +336,28 @@ static void accept_callback(socket_t fd, void* arg) { fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); } #endif + /* Check allowed IP (default deny all) */ + uint32_t client_ip = 0; + if (client_addr.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr; + client_ip = ntohl(sin->sin_addr.s_addr); + } else { + DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "IPv6 not supported for control allow list"); +#ifdef _WIN32 + closesocket(client_fd); +#else + close(client_fd); +#endif + return; + } + if (!is_control_ip_allowed(server, client_ip)) { +#ifdef _WIN32 + closesocket(client_fd); +#else + close(client_fd); +#endif + return; + } DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Accept..."); /* Check max clients */ diff --git a/utun.conf.sample b/utun.conf.sample index 5bd8c82..8ec0bef 100644 --- a/utun.conf.sample +++ b/utun.conf.sample @@ -1,21 +1,24 @@ [global] -tun_ip=10.0.0.1 -mtu=1500 # MTU for all connections (0 = use default 1500) -control_ip=127.0.0.1 -control_port=12345 -net_debug=0 +tun_ip=10.0.0.1 # IP адрес tun интерфейса. чтобы к нему могли обращаться другие узлу его надо вписать в секции [routing] my_subnet +mtu=1500 # UDP MTU for all connections (default - 1500) +# уникальный id и ключи вашего узла. если их нет они сгенерируются автоматически (random) при первом запуске и впишутся в конфиг my_node_id= my_private_key= my_public_key= - +# control socket для наблюдения и управления utun сервером +# например utun сервер стоит на роутере, и вы можете управлять и наблюдать за ним с рабочего компьютера +[control] +ip=127.0.0.1 +port=12345 +# control_allow=ip/mask (multiple allowed, default deny all) +control_allow=127.0.0.1/32 +# control_allow=192.168.1.0/24 [routing] -route_subnet=10.0.0.0/24 -route_subnet=10.23.0.0/16 -#allowed_subnet=10.23.0.0/16 -my_subnet=10.23.1.0/24 +route_subnet=10.0.0.0/16 # к каким подсетям utun вы хотите обращаться (добавит маршруты этих подсетей через utun) +my_subnet=10.0.0.0/24 # какие ваши подсети будут доступны другим узлам utun (в секции [firewall] можно тонко настроить разрешенные ip и порты) # секция server обязательна и у сервера и у клиента. это рабочий сокет. # мои адреса и каналы