|
|
|
@ -25,6 +25,7 @@ |
|
|
|
|
|
|
|
|
|
|
|
// Forward declarations
|
|
|
|
// Forward declarations
|
|
|
|
static uint32_t get_dest_ip(const uint8_t *packet, size_t len); |
|
|
|
static uint32_t get_dest_ip(const uint8_t *packet, size_t len); |
|
|
|
|
|
|
|
static int local_sockaddr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b); |
|
|
|
|
|
|
|
|
|
|
|
// Global instance for signal handlers
|
|
|
|
// Global instance for signal handlers
|
|
|
|
static struct UTUN_INSTANCE *g_instance = NULL; |
|
|
|
static struct UTUN_INSTANCE *g_instance = NULL; |
|
|
|
@ -38,6 +39,16 @@ void utun_instance_set_tun_init_enabled(int enabled) { |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization %s", enabled ? "enabled" : "disabled"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization %s", enabled ? "enabled" : "disabled"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int local_sockaddr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b) { |
|
|
|
|
|
|
|
if (!a || !b || a->ss_family != b->ss_family) return 0; |
|
|
|
|
|
|
|
if (a->ss_family == AF_INET) { |
|
|
|
|
|
|
|
const struct sockaddr_in *ia = (const struct sockaddr_in *)a; |
|
|
|
|
|
|
|
const struct sockaddr_in *ib = (const struct sockaddr_in *)b; |
|
|
|
|
|
|
|
return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 0; // IPv6 stub
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Common initialization function (called by both create functions)
|
|
|
|
// Common initialization function (called by both create functions)
|
|
|
|
// Returns 0 on success, -1 on error (instance is NOT freed on error - caller must handle)
|
|
|
|
// Returns 0 on success, -1 on error (instance is NOT freed on error - caller must handle)
|
|
|
|
static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* ua, struct utun_config* config) { |
|
|
|
static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* ua, struct utun_config* config) { |
|
|
|
@ -448,55 +459,147 @@ void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *ph |
|
|
|
report.etcp_connections_count); |
|
|
|
report.etcp_connections_count); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Reload with comparison for [server:] sockets, [client:] connections, link= in clients.
|
|
|
|
static int local_local_sockaddr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b) { |
|
|
|
* Unchanged: leave. Changed/deleted: close/reopen. New: add. Routing/fw always reloaded. |
|
|
|
if (!a || !b || a->ss_family != b->ss_family) return 0; |
|
|
|
* BGP restarts on conn change. Log always reopened + debug categories updated. Simple count+name compare (full fallback for complex link delta). */ |
|
|
|
if (a->ss_family == AF_INET) { |
|
|
|
|
|
|
|
const struct sockaddr_in *ia = (const struct sockaddr_in *)a; |
|
|
|
|
|
|
|
const struct sockaddr_in *ib = (const struct sockaddr_in *)b; |
|
|
|
|
|
|
|
return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 0; // IPv6 stub
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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) { |
|
|
|
struct UTUN_INSTANCE *utun_instance_reload(struct UTUN_INSTANCE *instance, struct UASYNC *ua, const char *config_file) { |
|
|
|
if (!instance || !ua || !config_file) { |
|
|
|
if (!instance || !ua || !config_file) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload: bad args"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "utun_instance_reload: invalid arguments"); |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Reload from %s", config_file); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "SIGHUP selective reload from %s", config_file); |
|
|
|
debug_reopen_log(); |
|
|
|
debug_reopen_log(); |
|
|
|
struct utun_config *new_config = parse_config(config_file); |
|
|
|
struct utun_config *new_config = parse_config(config_file); |
|
|
|
if (!new_config) { |
|
|
|
if (!new_config) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse new config"); |
|
|
|
return instance; |
|
|
|
return instance; |
|
|
|
} |
|
|
|
} |
|
|
|
int needs_full = 0; |
|
|
|
int needs_full = 0; |
|
|
|
if (strcmp(instance->config->global.my_public_key_hex, new_config->global.my_public_key_hex) != 0 || |
|
|
|
if (strcmp(instance->config->global.my_public_key_hex, new_config->global.my_public_key_hex) != 0 || |
|
|
|
instance->config->global.my_node_id != new_config->global.my_node_id) needs_full = 1; |
|
|
|
strcmp(instance->config->global.my_private_key_hex, new_config->global.my_private_key_hex) != 0 || |
|
|
|
int srv_old = 0, srv_new = 0, cli_old = 0, cli_new = 0; |
|
|
|
instance->config->global.my_node_id != new_config->global.my_node_id || |
|
|
|
for (struct CFG_SERVER *s = instance->config->servers; s; s = s->next) srv_old++; |
|
|
|
strcmp(instance->config->global.tun_ifname, new_config->global.tun_ifname) != 0) { |
|
|
|
for (struct CFG_SERVER *s = new_config->servers; s; s = s->next) srv_new++; |
|
|
|
needs_full = 1; |
|
|
|
for (struct CFG_CLIENT *c = instance->config->clients; c; c = c->next) cli_old++; |
|
|
|
} |
|
|
|
for (struct CFG_CLIENT *c = new_config->clients; c; c = c->next) cli_new++; |
|
|
|
|
|
|
|
if (srv_old != srv_new || cli_old != cli_new) needs_full = 1; |
|
|
|
|
|
|
|
if (needs_full) { |
|
|
|
if (needs_full) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Incompatible global - full reload"); |
|
|
|
utun_instance_destroy(instance); |
|
|
|
utun_instance_destroy(instance); |
|
|
|
struct UTUN_INSTANCE *ni = utun_instance_create(ua, config_file); |
|
|
|
struct UTUN_INSTANCE *new_instance = utun_instance_create(ua, config_file); |
|
|
|
if (!ni || utun_instance_init(ni) < 0) { |
|
|
|
if (!new_instance || utun_instance_init(new_instance) < 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "full reload failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Full reload failed"); |
|
|
|
if (ni) utun_instance_destroy(ni); |
|
|
|
if (new_instance) utun_instance_destroy(new_instance); |
|
|
|
free_config(new_config); |
|
|
|
free_config(new_config); |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
ni->running = 1; |
|
|
|
new_instance->running = 1; |
|
|
|
free_config(new_config); |
|
|
|
free_config(new_config); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Full reload done (sockets/clients changed)"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Full reload completed"); |
|
|
|
return ni; |
|
|
|
return new_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 || ns->loss_rate != os->loss_rate) { |
|
|
|
|
|
|
|
os->mtu = ns->mtu; |
|
|
|
|
|
|
|
os->loss_rate = ns->loss_rate; |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (instance->config) free_config(instance->config); |
|
|
|
struct ETCP_SOCKET *os = instance->etcp_sockets; |
|
|
|
instance->config = new_config; |
|
|
|
while (os) { |
|
|
|
if (new_config->global.debug_level[0]) debug_apply_global_level(new_config->global.debug_level); |
|
|
|
struct ETCP_SOCKET *next = os->next; |
|
|
|
for (int i = 0; i < new_config->global.debug_levels.count; i++) { |
|
|
|
int found = 0; |
|
|
|
debug_apply_category_config(new_config->global.debug_levels.category[i], new_config->global.debug_levels.level[i]); |
|
|
|
for (struct CFG_SERVER *ns = new_config->servers; ns; ns = ns->next) { |
|
|
|
|
|
|
|
if (strcmp(ns->name, os->name) == 0) { |
|
|
|
|
|
|
|
found = 1; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!found) { |
|
|
|
|
|
|
|
etcp_socket_remove(os); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Removed deleted socket %s", os->name); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
os = next; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Clients [client:] and links (link=)
|
|
|
|
|
|
|
|
for (struct CFG_CLIENT *nc = new_config->clients; nc; nc = nc->next) { |
|
|
|
|
|
|
|
struct ETCP_CONN *conn = NULL; |
|
|
|
|
|
|
|
for (struct ETCP_CONN *c = instance->connections; c; c = c->next) { |
|
|
|
|
|
|
|
if (strcmp(nc->name, c->name) == 0) { |
|
|
|
|
|
|
|
conn = c; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!conn) { |
|
|
|
|
|
|
|
conn = etcp_connection_create(instance, nc->name); |
|
|
|
|
|
|
|
if (conn) sc_set_peer_public_key(&conn->crypto_ctx, nc->peer_public_key_hex, 1); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Added new client %s", nc->name); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// links
|
|
|
|
|
|
|
|
for (struct CFG_CLIENT_LINK *nl = nc->links; nl; nl = nl->next) { |
|
|
|
|
|
|
|
struct ETCP_LINK *link = NULL; |
|
|
|
|
|
|
|
for (struct ETCP_LINK *l = conn->links; l; l = l->next) { |
|
|
|
|
|
|
|
if (local_sockaddr_equal(&nl->remote_addr, &l->remote_addr)) { |
|
|
|
|
|
|
|
link = l; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (link) { |
|
|
|
|
|
|
|
// in-place for unchanged link (no tear)
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Link for %s unchanged - untouched", nc->name); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
struct ETCP_SOCKET *sock = NULL; |
|
|
|
|
|
|
|
for (struct ETCP_SOCKET *s = instance->etcp_sockets; s; s = s->next) { |
|
|
|
|
|
|
|
if (strcmp(nl->server_name, s->name) == 0) { |
|
|
|
|
|
|
|
sock = s; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (sock) { |
|
|
|
|
|
|
|
etcp_link_new(conn, sock, &nl->remote_addr, 0); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Added new link for %s", nc->name); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// remove deleted links (safe with next)
|
|
|
|
|
|
|
|
struct ETCP_LINK *l = conn->links; |
|
|
|
|
|
|
|
while (l) { |
|
|
|
|
|
|
|
struct ETCP_LINK *next_l = l->next; |
|
|
|
|
|
|
|
int found = 0; |
|
|
|
|
|
|
|
for (struct CFG_CLIENT_LINK *nl = nc->links; nl; nl = nl->next) { |
|
|
|
|
|
|
|
if (local_sockaddr_equal(&nl->remote_addr, &l->remote_addr)) { |
|
|
|
|
|
|
|
found = 1; break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!found) { |
|
|
|
|
|
|
|
etcp_link_close(l); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Removed deleted link for %s", nc->name); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
l = next_l; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
fw_free(&instance->fw); |
|
|
|
fw_free(&instance->fw); |
|
|
|
fw_init(&instance->fw); |
|
|
|
fw_init(&instance->fw); |
|
|
|
fw_load_rules(&instance->fw, &new_config->global); |
|
|
|
fw_load_rules(&instance->fw, &new_config->global); |
|
|
|
routing_destroy(instance); // will recreate in partial if needed, but BGP self-restarts
|
|
|
|
routing_destroy(instance); |
|
|
|
routing_create(instance); |
|
|
|
routing_create(instance); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Partial reload: sockets/clients/links compared (unchanged preserved), log+debug+fw+routing updated"); |
|
|
|
if (instance->config) free_config(instance->config); |
|
|
|
|
|
|
|
instance->config = new_config; |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Selective partial reload completed (only changed updated, unchanged untouched)"); |
|
|
|
return instance; |
|
|
|
return instance; |
|
|
|
} |
|
|
|
} |
|
|
|
|