Reload complete endpoint settings and restart changed resource owners
This commit is contained in:
1 parent
5e63bc2a42
commit
a7d2333deb
1 file changed
+143
-126
+143
-126
@@ -1004,144 +1004,161 @@ void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *ph
|
||||
report.etcp_connections_count);
|
||||
}
|
||||
|
||||
/* Variant B: selective reload. Compares settings for [server:], [client:], link=. Unchanged untouched (no close, no timers, no queues). Changed/deleted: target only that object. New: add. Routing/fw always. BGP self-restarts. */
|
||||
struct UTUN_INSTANCE *utun_instance_reload(struct UTUN_INSTANCE *instance, struct UASYNC *ua, const char *config_file) {
|
||||
if (!instance || !ua || !config_file) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid arguments");
|
||||
return NULL;
|
||||
/* Сравнивает фактические настройки endpoint, независимо от выбранного ОС ephemeral порта. */
|
||||
static int instance_server_equal(const struct CFG_SERVER* a, const struct CFG_SERVER* b) {
|
||||
return a && b && !strcmp(a->name, b->name) && local_sockaddr_equal(&a->ip, &b->ip) &&
|
||||
a->netif_index == b->netif_index && a->so_mark == b->so_mark && a->fib == b->fib && a->type == b->type &&
|
||||
a->transport == b->transport && a->reality_enabled == b->reality_enabled && a->socks_enabled == b->socks_enabled &&
|
||||
a->ipv6_mode == b->ipv6_mode && a->mtu == b->mtu && a->only_local == b->only_local;
|
||||
}
|
||||
|
||||
static int instance_servers_equal(const struct CFG_SERVER* a, const struct CFG_SERVER* b) {
|
||||
while (a && b) { if (!instance_server_equal(a, b)) return 0; a = a->next; b = b->next; }
|
||||
return !a && !b;
|
||||
}
|
||||
|
||||
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 ||
|
||||
memcmp(&a->ip.addr, &b->ip.addr, a->ip.family == AF_INET ? 4 : 16)) return 0;
|
||||
a = a->next; b = b->next;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "SIGHUP selective reload from %s", config_file);
|
||||
return !a && !b;
|
||||
}
|
||||
|
||||
/* Горячие global-поля: firewall, уровни debug и STUN. Остальным владельцам нужен restart.
|
||||
* Указатели сравниваются по содержимому; padding модели может лишь консервативно выбрать restart. */
|
||||
static int instance_globals_equal(const struct global_config* a, const struct global_config* b) {
|
||||
if (a->control_allow_count != b->control_allow_count ||
|
||||
(a->control_allow_count && memcmp(a->control_allows, b->control_allows, a->control_allow_count * sizeof(*a->control_allows)))) return 0;
|
||||
const struct CFG_NTP_SERVER* na = a->ntp_servers; const struct CFG_NTP_SERVER* nb = b->ntp_servers;
|
||||
while (na && nb) { if (strcmp(na->name, nb->name)) return 0; na = na->next; nb = nb->next; }
|
||||
if (na || nb) return 0;
|
||||
const struct CFG_ALLOWED_KEY* ka = a->allowed_keys; const struct CFG_ALLOWED_KEY* kb = b->allowed_keys;
|
||||
while (ka && kb) { if (memcmp(ka->key_bin, kb->key_bin, sizeof(ka->key_bin))) return 0; ka = ka->next; kb = kb->next; }
|
||||
if (ka || kb) return 0;
|
||||
struct global_config x = *a, y = *b;
|
||||
x.control_allows = y.control_allows = NULL; x.ntp_servers = y.ntp_servers = NULL; x.allowed_keys = y.allowed_keys = NULL;
|
||||
x.firewall_rules = y.firewall_rules = NULL; x.firewall_rule_count = y.firewall_rule_count = 0;
|
||||
x.firewall_bypass_all = y.firewall_bypass_all = 0;
|
||||
x.stun_servers = y.stun_servers = NULL; x.stun_server_count = y.stun_server_count = 0; x.stun_enabled = y.stun_enabled = 0;
|
||||
memset(x.debug_level, 0, sizeof(x.debug_level)); memset(y.debug_level, 0, sizeof(y.debug_level));
|
||||
memset(&x.debug_levels, 0, sizeof(x.debug_levels)); memset(&y.debug_levels, 0, sizeof(y.debug_levels));
|
||||
return !memcmp(&x, &y, sizeof(x));
|
||||
}
|
||||
|
||||
static int instance_clients_equal(const struct CFG_CLIENT* a, const struct CFG_CLIENT* b) {
|
||||
while (a && b) {
|
||||
if (strcmp(a->name, b->name) || strcmp(a->peer_public_key_hex, b->peer_public_key_hex) ||
|
||||
a->keepalive != b->keepalive || a->reality_enabled != b->reality_enabled ||
|
||||
memcmp(&a->reality, &b->reality, sizeof(a->reality))) return 0;
|
||||
const struct CFG_CLIENT_LINK* la = a->links; const struct CFG_CLIENT_LINK* lb = b->links;
|
||||
while (la && lb) {
|
||||
const char* sa = la->local_srv ? la->local_srv->name : la->server_name;
|
||||
const char* sb = lb->local_srv ? lb->local_srv->name : lb->server_name;
|
||||
if (strcmp(sa, sb) || !local_sockaddr_equal(&la->remote_addr, &lb->remote_addr)) return 0;
|
||||
la = la->next; lb = lb->next;
|
||||
}
|
||||
if (la || lb) return 0;
|
||||
a = a->next; b = b->next;
|
||||
}
|
||||
return !a && !b;
|
||||
}
|
||||
|
||||
/* Готовит конфиг/firewall/индекс до мутаций. Изменения владельцев требуют полного restart;
|
||||
* ручные endpoint обновляются поимённо, без сравнения port=0 с фактическим портом ОС. */
|
||||
struct UTUN_INSTANCE* utun_instance_reload(struct UTUN_INSTANCE* instance, struct UASYNC* ua, const char* config_file) {
|
||||
if (!instance || !ua || ua != instance->ua || !config_file || !*config_file) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload requires instance, its owning uasync and a config path"); return instance;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "reload preparing %s", config_file);
|
||||
debug_reopen_log();
|
||||
struct utun_config *new_config = parse_config(config_file);
|
||||
if (!new_config) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse new config");
|
||||
return instance;
|
||||
if (config_ensure_keys_and_node_id(config_file) < 0) return instance;
|
||||
struct utun_config* next = parse_config(config_file);
|
||||
if (!next) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload parse failed; previous instance retained"); return instance; }
|
||||
struct firewall_ctx firewall = {0};
|
||||
if (fw_load_rules(&firewall, &next->global) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload firewall preparation failed; previous instance retained");
|
||||
fw_free(&firewall); free_config(next); return instance;
|
||||
}
|
||||
struct firewall_ctx next_fw = {0};
|
||||
if (fw_load_rules(&next_fw, &new_config->global) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Reload rejected: cannot prepare firewall; previous configuration retained");
|
||||
free_config(new_config);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Reset + apply debug categories from [debug] section (always, for partial and full paths).
|
||||
// Mirrors utun_instance_create exactly. Reset ensures removed categories fall back to global.
|
||||
for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) {
|
||||
g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE;
|
||||
}
|
||||
if (new_config->global.debug_level[0]) {
|
||||
debug_apply_global_level(new_config->global.debug_level);
|
||||
}
|
||||
for (int i = 0; i < new_config->global.debug_levels.count; i++) {
|
||||
debug_apply_category_config(
|
||||
new_config->global.debug_levels.category[i],
|
||||
new_config->global.debug_levels.level[i]
|
||||
);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied [debug] section (%d categories)", new_config->global.debug_levels.count);
|
||||
|
||||
int needs_full = 0;
|
||||
if (strcmp(instance->config->global.my_public_key_hex, new_config->global.my_public_key_hex) != 0 ||
|
||||
strcmp(instance->config->global.my_private_key_hex, new_config->global.my_private_key_hex) != 0 ||
|
||||
instance->config->global.my_node_id != new_config->global.my_node_id ||
|
||||
strcmp(instance->config->global.tun_ifname, new_config->global.tun_ifname) != 0) {
|
||||
needs_full = 1;
|
||||
}
|
||||
if (needs_full) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Incompatible global - full reload");
|
||||
int restart_core = instance->core_started, restart_utun = instance->utun_started, restart_chat = instance->chat_started;
|
||||
fw_free(&next_fw);
|
||||
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_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);
|
||||
for (unsigned i = 0; i < DEBUG_CATEGORY_COUNT; i++) g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE;
|
||||
if (next->global.debug_level[0]) debug_apply_global_level(next->global.debug_level);
|
||||
for (int i = 0; i < next->global.debug_levels.count; i++)
|
||||
debug_apply_category_config(next->global.debug_levels.category[i], next->global.debug_levels.level[i]);
|
||||
if (full) {
|
||||
int core = instance->core_started, utun = instance->utun_started, chat = instance->chat_started, running = instance->running;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "full reload: core=%d utun=%d chat=%d running=%d", core, utun, chat, running);
|
||||
fw_free(&firewall);
|
||||
utun_instance_destroy(instance);
|
||||
struct UTUN_INSTANCE *new_instance = utun_instance_create(ua, config_file);
|
||||
if (!new_instance || (restart_core && utun_core_start(new_instance) < 0) ||
|
||||
(restart_utun && utun_service_start(new_instance) < 0) || (restart_chat && chat_service_start(new_instance) < 0)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Full reload failed");
|
||||
if (new_instance) utun_instance_destroy(new_instance);
|
||||
free_config(new_config);
|
||||
return NULL;
|
||||
instance = utun_instance_create_from_config(ua, next);
|
||||
if (!instance) { free_config(next); DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "full reload create failed; previous instance destroyed"); return NULL; }
|
||||
if ((core && utun_core_start(instance) < 0) || (utun && utun_service_start(instance) < 0) ||
|
||||
(chat && chat_service_start(instance) < 0)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "full reload service restart failed"); utun_instance_destroy(instance); return NULL;
|
||||
}
|
||||
new_instance->running = 1;
|
||||
free_config(new_config);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Full reload completed");
|
||||
return new_instance;
|
||||
instance->running = running;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "full reload completed"); return instance;
|
||||
}
|
||||
// Selective Variant B (settings compare, only affected updated)
|
||||
// Servers [server:]
|
||||
for (struct CFG_SERVER *ns = new_config->servers; ns; ns = ns->next) {
|
||||
int matched = 0;
|
||||
for (struct ETCP_SOCKET *os = instance->etcp_sockets; os; os = os->next) {
|
||||
if (strcmp(ns->name, os->name) == 0 && local_sockaddr_equal(&ns->ip, &os->local_addr)) {
|
||||
matched = 1;
|
||||
if (ns->mtu != os->mtu) {
|
||||
os->mtu = ns->mtu;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Updated socket settings for %s", ns->name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
etcp_socket_add(instance, ns);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Added new socket %s", ns->name);
|
||||
}
|
||||
struct ll_queue* networks = instance_build_networks(ua, next->networks);
|
||||
if (!networks) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload network preparation failed; previous instance retained");
|
||||
fw_free(&firewall); free_config(next); return instance;
|
||||
}
|
||||
struct ETCP_SOCKET *os = instance->etcp_sockets;
|
||||
while (os) {
|
||||
struct ETCP_SOCKET *next = os->next;
|
||||
int found = 0;
|
||||
for (struct CFG_SERVER *ns = new_config->servers; ns; ns = ns->next) {
|
||||
if (strcmp(ns->name, os->name) == 0) {
|
||||
found = 1; break;
|
||||
int socket_result = 0;
|
||||
if (next->global.auto_sockets == CFG_AUTO_SOCKETS_OFF) {
|
||||
for (unsigned id = 0; id < 256; id++) {
|
||||
struct ETCP_SOCKET* socket = instance->etcp_sockets;
|
||||
while (socket && socket->sock_id != id) socket = socket->next;
|
||||
if (!socket || socket->probe_only) continue;
|
||||
const struct CFG_SERVER* old = previous->servers; const struct CFG_SERVER* updated = next->servers;
|
||||
while (old && strcmp(old->name, socket->name)) old = old->next;
|
||||
if (!old) continue; // endpoint другого владельца не относится к server-конфигу
|
||||
while (updated && strcmp(updated->name, socket->name)) updated = updated->next;
|
||||
if (!instance_server_equal(old, updated)) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "reload removing changed/deleted endpoint %s id=%u", socket->name, id);
|
||||
instance_remove_socket(instance, socket); servers_changed = 1;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
etcp_socket_remove(os);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Removed deleted socket %s", os->name);
|
||||
for (struct CFG_SERVER* server = next->servers; server; server = server->next) {
|
||||
struct ETCP_SOCKET* socket = instance->etcp_sockets;
|
||||
while (socket && strcmp(socket->name, server->name)) socket = socket->next;
|
||||
if (socket) continue;
|
||||
servers_changed = 1;
|
||||
if (etcp_create_server_socket(instance, server, &socket) < 0 || !socket) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload cannot create endpoint %s; new config retained for retry", server->name);
|
||||
socket_result = -1; continue;
|
||||
}
|
||||
if (ncd_add_socket_links(instance, socket) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload endpoint %s ready, but some NCD links failed", server->name); socket_result = -1;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "reload endpoint created name=%s tcp=%u id=%u generation=%llu", socket->name,
|
||||
socket->is_tcp, socket->sock_id, (unsigned long long)socket->generation);
|
||||
}
|
||||
os = next;
|
||||
instance->socket_init_status = socket_result;
|
||||
}
|
||||
int client_result = instance->core_started ? init_client_connections(instance, new_config->clients) : 0;
|
||||
if (client_result < 0)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload: failed to replace client handles; previous connections retained");
|
||||
// Reload networks: clear and repopulate
|
||||
if (instance->networks) {
|
||||
struct ll_entry *entry;
|
||||
while ((entry = queue_data_get(instance->networks)) != NULL) {
|
||||
queue_entry_free(entry);
|
||||
}
|
||||
struct CFG_NETWORK *net = new_config->networks;
|
||||
while (net) {
|
||||
struct ll_entry *new_entry = queue_entry_new(sizeof(struct NETWORK_ENTRY));
|
||||
if (!new_entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate network entry for reload: %s", net->name);
|
||||
net = net->next;
|
||||
continue;
|
||||
}
|
||||
struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)new_entry->data;
|
||||
ne->id = net->id & 0x00FFFFFFFFFFFFFFULL;
|
||||
for (int i = 0; i < SC_PUBKEY_SIZE; i++) {
|
||||
unsigned int b; sscanf(net->pubkey_hex + i * 2, "%2x", &b); ne->pubkey[i] = (uint8_t)b;
|
||||
}
|
||||
for (int i = 0; i < SC_PRIVKEY_SIZE; i++) {
|
||||
unsigned int b; sscanf(net->signing_key_hex + i * 2, "%2x", &b); ne->signing_key[i] = (uint8_t)b;
|
||||
}
|
||||
strncpy(ne->name, net->name, sizeof(ne->name) - 1);
|
||||
ne->name[sizeof(ne->name) - 1] = '\0';
|
||||
queue_data_put_with_index(instance->networks, new_entry);
|
||||
net = net->next;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks reloaded");
|
||||
}
|
||||
fw_free(&instance->fw);
|
||||
instance->fw = next_fw;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall reload applied: rules=%d bypass_all=%d", instance->fw.count, instance->fw.bypass_all);
|
||||
// routing_destroy(instance); // commented per request - BGP self-restarts on conn events
|
||||
// routing_create(instance); // commented per request
|
||||
int clients_changed = servers_changed || !instance_clients_equal(previous->clients, next->clients);
|
||||
int client_result = instance->core_started && clients_changed ? init_client_connections(instance, next->clients) : 0;
|
||||
stun_nat_destroy(instance);
|
||||
if (instance->config) free_config(instance->config);
|
||||
instance->config = new_config;
|
||||
if (instance->core_started && stun_nat_init(instance) < 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "STUN restart failed after config reload");
|
||||
if (client_result < 0) DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reload completed with client errors; previous client handles retained");
|
||||
else DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Selective partial reload completed (only changed updated, unchanged untouched, [debug] applied)");
|
||||
instance->config = next;
|
||||
free_config(previous);
|
||||
queue_clear(instance->networks); queue_free(instance->networks); instance->networks = networks;
|
||||
fw_free(&instance->fw); instance->fw = firewall;
|
||||
if (servers_changed) {
|
||||
if (instance->topo_groups && topo_node_update_my_addresses(instance) < 0)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload could not publish local endpoint snapshot");
|
||||
for (struct ll_entry* e = instance->topo_groups ? instance->topo_groups->group_list->head : NULL; e; e = e->next)
|
||||
conn_mgr_network_changed(((struct TOPO_GROUP*)e)->conn_mgr);
|
||||
}
|
||||
if (instance->core_started && stun_nat_init(instance) < 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "STUN restart failed after reload");
|
||||
if (socket_result < 0 || client_result < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "partial reload completed with errors: sockets=%d clients=%d", socket_result, client_result);
|
||||
else DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "partial reload completed: endpoints_changed=%d clients_changed=%d", servers_changed, clients_changed);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user