|
|
|
@ -144,6 +144,7 @@ struct auto_sock_iface { |
|
|
|
struct AUTO_SOCKET { |
|
|
|
struct AUTO_SOCKET { |
|
|
|
struct UTUN_INSTANCE* instance; // родительский utun instance
|
|
|
|
struct UTUN_INSTANCE* instance; // родительский utun instance
|
|
|
|
struct auto_sock_iface* ifaces; // связный список интерфейсов
|
|
|
|
struct auto_sock_iface* ifaces; // связный список интерфейсов
|
|
|
|
|
|
|
|
uint8_t mode; // CFG_AUTO_SOCKETS_SYSTEM или CFG_AUTO_SOCKETS_ANDROID
|
|
|
|
uint8_t v4_usable; // есть ли сейчас рабочий v4-сокет с реальным IP
|
|
|
|
uint8_t v4_usable; // есть ли сейчас рабочий v4-сокет с реальным IP
|
|
|
|
uint8_t v6_usable; // есть ли сейчас рабочий v6-сокет с реальным IP
|
|
|
|
uint8_t v6_usable; // есть ли сейчас рабочий v6-сокет с реальным IP
|
|
|
|
uint8_t v4_addr_changed; // v4-адрес или тип изменился с прошлой проверки
|
|
|
|
uint8_t v4_addr_changed; // v4-адрес или тип изменился с прошлой проверки
|
|
|
|
@ -167,6 +168,7 @@ static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex); |
|
|
|
static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, int* out_has_v4, int* out_has_v6, uint8_t* out_v4_type, uint8_t* out_v6_type); |
|
|
|
static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, int* out_has_v4, int* out_has_v6, uint8_t* out_v4_type, uint8_t* out_v6_type); |
|
|
|
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname); |
|
|
|
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname); |
|
|
|
static void auto_socket_scan_all(struct AUTO_SOCKET* as); |
|
|
|
static void auto_socket_scan_all(struct AUTO_SOCKET* as); |
|
|
|
|
|
|
|
static void auto_socket_reconcile_config(struct AUTO_SOCKET* as); |
|
|
|
|
|
|
|
|
|
|
|
/* ─── DB helpers (port persistence) ───
|
|
|
|
/* ─── DB helpers (port persistence) ───
|
|
|
|
* Сохраняют порт сокета в SQLite чтобы переиспользовать его при перезапуске utun. |
|
|
|
* Сохраняют порт сокета в SQLite чтобы переиспользовать его при перезапуске utun. |
|
|
|
@ -1318,14 +1320,23 @@ static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) { (void)as; } |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
int auto_socket_init(struct UTUN_INSTANCE* inst) { |
|
|
|
int auto_socket_init(struct UTUN_INSTANCE* inst) { |
|
|
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] instance is NULL"); return -1; } |
|
|
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] instance is NULL"); return -1; } |
|
|
|
if (!inst->config || !inst->config->global.auto_sockets) return 0; |
|
|
|
if (!inst->config || inst->config->global.auto_sockets == CFG_AUTO_SOCKETS_OFF) return 0; |
|
|
|
if (inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] already initialized"); return -1; } |
|
|
|
if (inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] already initialized"); return -1; } |
|
|
|
|
|
|
|
|
|
|
|
struct AUTO_SOCKET* as = u_calloc(1, sizeof(*as)); |
|
|
|
struct AUTO_SOCKET* as = u_calloc(1, sizeof(*as)); |
|
|
|
if (!as) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return -1; } |
|
|
|
if (!as) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return -1; } |
|
|
|
as->instance = inst; |
|
|
|
as->instance = inst; |
|
|
|
|
|
|
|
as->mode = (uint8_t)inst->config->global.auto_sockets; |
|
|
|
inst->auto_socket_state = as; |
|
|
|
inst->auto_socket_state = as; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Android-режим: источник сокетов — [server] секции конфига (генерирует Kotlin).
|
|
|
|
|
|
|
|
* Системный мониторинг и таблица портов в БД не нужны. */ |
|
|
|
|
|
|
|
if (as->mode == CFG_AUTO_SOCKETS_ANDROID) { |
|
|
|
|
|
|
|
auto_socket_reconcile_config(as); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized (android mode)"); |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (inst->topo_sqlite_db) { |
|
|
|
if (inst->topo_sqlite_db) { |
|
|
|
sqlite3_exec(inst->topo_sqlite_db, |
|
|
|
sqlite3_exec(inst->topo_sqlite_db, |
|
|
|
"CREATE TABLE IF NOT EXISTS auto_socket_ports (" |
|
|
|
"CREATE TABLE IF NOT EXISTS auto_socket_ports (" |
|
|
|
@ -1375,5 +1386,112 @@ static void auto_socket_scan_all_trampoline(void* arg) { |
|
|
|
void auto_socket_on_network_change(struct UTUN_INSTANCE* inst) { |
|
|
|
void auto_socket_on_network_change(struct UTUN_INSTANCE* inst) { |
|
|
|
if (!inst || !inst->auto_socket_state) return; |
|
|
|
if (!inst || !inst->auto_socket_state) return; |
|
|
|
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; |
|
|
|
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; |
|
|
|
|
|
|
|
/* Android-режим: обновление идёт только через auto_socket_reload_config (Kotlin → JNI). */ |
|
|
|
|
|
|
|
if (as->mode == CFG_AUTO_SOCKETS_ANDROID) return; |
|
|
|
uasync_post(as->instance->ua, auto_socket_scan_all_trampoline, as); |
|
|
|
uasync_post(as->instance->ua, auto_socket_scan_all_trampoline, as); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ═══════════ Android-режим: reconcile сокетов из [server] секций конфига ═══════════ */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Сравнивает CFG_SERVER с существующим ETCP_SOCKET: имя, адрес+порт, тип, транспорт. */ |
|
|
|
|
|
|
|
static int server_matches_socket(const struct CFG_SERVER* server, const struct ETCP_SOCKET* sock) { |
|
|
|
|
|
|
|
if (!server || !sock) return 0; |
|
|
|
|
|
|
|
if (strcmp(server->name, sock->name) != 0) return 0; |
|
|
|
|
|
|
|
if (server->transport != (uint8_t)sock->is_tcp) return 0; |
|
|
|
|
|
|
|
if (server->type != sock->type) return 0; |
|
|
|
|
|
|
|
const struct sockaddr_storage* a = &server->ip; |
|
|
|
|
|
|
|
const struct sockaddr_storage* b = sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr; |
|
|
|
|
|
|
|
if (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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (a->ss_family == AF_INET6) { |
|
|
|
|
|
|
|
const struct sockaddr_in6* ia = (const struct sockaddr_in6*)a; |
|
|
|
|
|
|
|
const struct sockaddr_in6* ib = (const struct sockaddr_in6*)b; |
|
|
|
|
|
|
|
return memcmp(&ia->sin6_addr, &ib->sin6_addr, 16) == 0 && ia->sin6_port == ib->sin6_port; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Удаляет один сокет (UDP или TCP) и все его линки. */ |
|
|
|
|
|
|
|
static void remove_config_socket(struct AUTO_SOCKET* as, struct ETCP_SOCKET* sock) { |
|
|
|
|
|
|
|
if (!sock) return; |
|
|
|
|
|
|
|
ncd_remove_socket_links(as->instance, sock); |
|
|
|
|
|
|
|
if (sock->is_tcp) { |
|
|
|
|
|
|
|
struct stcp_server* tsrv = stcp_server_list_find_by_sock(as->instance, sock); |
|
|
|
|
|
|
|
if (tsrv) { |
|
|
|
|
|
|
|
stcp_server_list_remove(as->instance, tsrv); |
|
|
|
|
|
|
|
stcp_link_server_destroy(tsrv); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
tcp_socket_remove(sock); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
etcp_socket_remove(sock); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Сверяет inst->config->servers с inst->etcp_sockets и устраняет расхождения:
|
|
|
|
|
|
|
|
* удаляет сокеты, которых нет/которые изменились (проход 1), создаёт отсутствующие (проход 2). */ |
|
|
|
|
|
|
|
static void auto_socket_reconcile_config(struct AUTO_SOCKET* as) { |
|
|
|
|
|
|
|
struct UTUN_INSTANCE* inst = as->instance; |
|
|
|
|
|
|
|
if (!inst || !inst->config) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Проход 1: удалить сокеты, которых нет в новом списке, либо которые изменились. */ |
|
|
|
|
|
|
|
struct ETCP_SOCKET* sock = inst->etcp_sockets; |
|
|
|
|
|
|
|
while (sock) { |
|
|
|
|
|
|
|
struct ETCP_SOCKET* next = sock->next; |
|
|
|
|
|
|
|
const struct CFG_SERVER* srv = NULL; |
|
|
|
|
|
|
|
for (const struct CFG_SERVER* s = inst->config->servers; s; s = s->next) |
|
|
|
|
|
|
|
if (strcmp(s->name, sock->name) == 0) { srv = s; break; } |
|
|
|
|
|
|
|
ip_str_t old_addr = sockaddr_storage_to_str(sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr); |
|
|
|
|
|
|
|
if (!srv) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing socket %s (%s) addr=%s (no longer in config)", |
|
|
|
|
|
|
|
sock->name, sock->is_tcp ? "tcp" : "udp", old_addr.str); |
|
|
|
|
|
|
|
remove_config_socket(as, sock); |
|
|
|
|
|
|
|
} else if (!server_matches_socket(srv, sock)) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] socket %s changed: %s → %s — rebuild", |
|
|
|
|
|
|
|
sock->name, old_addr.str, sockaddr_storage_to_str(&srv->ip).str); |
|
|
|
|
|
|
|
remove_config_socket(as, sock); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sock = next; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Проход 2: создать сокеты, которых ещё нет. */ |
|
|
|
|
|
|
|
for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) { |
|
|
|
|
|
|
|
int exists = 0; |
|
|
|
|
|
|
|
for (struct ETCP_SOCKET* es = inst->etcp_sockets; es; es = es->next) |
|
|
|
|
|
|
|
if (strcmp(es->name, s->name) == 0) { exists = 1; break; } |
|
|
|
|
|
|
|
if (exists) continue; |
|
|
|
|
|
|
|
struct ETCP_SOCKET* created = NULL; |
|
|
|
|
|
|
|
if (etcp_create_server_socket(inst, s, &created) == 0 && created) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] created socket %s (%s) addr=%s", |
|
|
|
|
|
|
|
s->name, s->transport ? "tcp" : "udp", sockaddr_storage_to_str(&s->ip).str); |
|
|
|
|
|
|
|
ncd_add_socket_links(inst, created); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto_socket_post_sockets_changed(as); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg) { |
|
|
|
|
|
|
|
if (!new_cfg) return; |
|
|
|
|
|
|
|
if (!inst || !inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] reload_config: invalid args"); free_config(new_cfg); return; } |
|
|
|
|
|
|
|
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; |
|
|
|
|
|
|
|
if (as->mode != CFG_AUTO_SOCKETS_ANDROID) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] reload_config: not in android mode"); free_config(new_cfg); return; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Сводка до/после: сколько серверов было → стало. */ |
|
|
|
|
|
|
|
int old_count = 0; for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) old_count++; |
|
|
|
|
|
|
|
int new_count = 0; for (struct CFG_SERVER* s = new_cfg->servers; s; s = s->next) new_count++; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Переносим новый список серверов в inst->config, старый освобождаем. */ |
|
|
|
|
|
|
|
struct CFG_SERVER* old = inst->config->servers; |
|
|
|
|
|
|
|
inst->config->servers = new_cfg->servers; |
|
|
|
|
|
|
|
new_cfg->servers = NULL; |
|
|
|
|
|
|
|
while (old) { struct CFG_SERVER* n = old->next; u_free(old); old = n; } |
|
|
|
|
|
|
|
free_config(new_cfg); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] reload_config: %d → %d servers", old_count, new_count); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto_socket_reconcile_config(as); |
|
|
|
|
|
|
|
} |
|
|
|
|