diff --git a/src/utun_instance.c b/src/utun_instance.c index ed354ce5..02b272e4 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -1031,6 +1031,16 @@ static int instance_servers_equal(const struct CFG_SERVER* a, const struct CFG_S return !a && !b; } +/* Parser сохраняет повторные server-секции отдельно; для них поимённый partial reload неоднозначен. */ +static int instance_server_names_unique(const struct CFG_SERVER* servers) { + for (const struct CFG_SERVER* a = servers; a; a = a->next) + for (const struct CFG_SERVER* b = a->next; b; b = b->next) + if (!strcmp(a->name, b->name)) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "duplicate server name '%s': reload requires full restart", a->name); return 0; + } + return 1; +} + static int instance_routes_equal(const struct CFG_ROUTE_ENTRY* a, const struct CFG_ROUTE_ENTRY* b) { while (a && b) { if (a->netmask != b->netmask || a->ip.family != b->ip.family || @@ -1098,6 +1108,7 @@ struct UTUN_INSTANCE* utun_instance_reload(struct UTUN_INSTANCE* instance, struc struct utun_config* previous = instance->config; int servers_changed = !instance_servers_equal(previous->servers, next->servers); int full = !instance_globals_equal(&previous->global, &next->global) || + !instance_server_names_unique(previous->servers) || !instance_server_names_unique(next->servers) || !instance_routes_equal(previous->route_subnets, next->route_subnets) || !instance_routes_equal(previous->my_subnets, next->my_subnets) || (next->global.auto_sockets != CFG_AUTO_SOCKETS_OFF && servers_changed); diff --git a/tests/test_instance_contract.c b/tests/test_instance_contract.c index e0fa3797..eff126ba 100644 --- a/tests/test_instance_contract.c +++ b/tests/test_instance_contract.c @@ -149,6 +149,12 @@ static void test_reload(struct UASYNC* ua) { write_config(path, "same", one, ""); assert(utun_instance_reload(inst, ua, path) == inst && endpoint_count(inst) == 1); assert(!endpoint(inst, "tcp")->is_tcp && !stcp_server_list_count(inst)); + const char* duplicates = "[server:shared]\naddr=127.0.0.1:0\ntype=private\n" + "[server:shared]\naddr=127.0.0.1:0\ntype=private\n"; + write_config(path, "same", duplicates, ""); + inst = utun_instance_reload(inst, ua, path); assert(inst && inst->core_started && endpoint_count(inst) == 2); + write_config(path, "same", one, ""); + inst = utun_instance_reload(inst, ua, path); assert(inst && inst->core_started && endpoint_count(inst) == 1); sqlite3_stmt* statement = NULL; assert(sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT 1", -1, &statement, NULL) == SQLITE_OK); utun_instance_stop(inst); assert(!inst->running);