Browse Source

headless: expose OS-assigned control and audio listener ports

master
evgeny 4 days ago
parent
commit
dc0dbbf720
  1. 13
      lib/socket_compat.c
  2. 2
      lib/socket_compat.h
  3. 14
      src/call/call_headless.c
  4. 2
      src/call/call_headless.h
  5. 14
      src/chat/chat_headless_control.c
  6. 2
      src/chat/chat_headless_control.h
  7. 14
      src/radio/radio_headless.c
  8. 2
      src/radio/radio_headless.h
  9. 11
      src/transport_layer/stcp_server.c
  10. 31
      tests/test_call_headless.c
  11. 9
      tests/test_net.h
  12. 30
      tests/test_radio_headless.c

13
lib/socket_compat.c

@ -246,3 +246,16 @@ uint16_t ss_get_port(const struct sockaddr_storage *addr) {
return 0;
}
uint16_t socket_get_bound_port(socket_t sock) {
struct sockaddr_storage address; socklen_t size = sizeof(address);
if (getsockname(sock, (struct sockaddr*)&address, &size) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "getsockname fd=%llu failed: %s",
(unsigned long long)sock, socket_strerror(socket_get_error()));
return 0;
}
uint16_t port = ss_get_port(&address);
if (!port) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket fd=%llu family=%d has no bound port",
(unsigned long long)sock, address.ss_family);
return port;
}

2
lib/socket_compat.h

@ -78,6 +78,8 @@ const char* socket_strerror(int err);
// взять порт из sockaddr_storage
uint16_t ss_get_port(const struct sockaddr_storage *addr);
// Фактический IPv4/IPv6-порт уже привязанного сокета; 0 при ошибке.
uint16_t socket_get_bound_port(socket_t sock);
#ifdef __cplusplus

14
src/call/call_headless.c

@ -312,7 +312,9 @@ static void ca_cleanup(void* arg) {
int call_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port) {
if (!ua || !inst || !bind_ip || port <= 0 || port > 65535) return -1;
if (!ua || !inst || !bind_ip || port < 0 || port > 65535) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: invalid init arguments port=%d", CH_ID, port); return -1;
}
struct call_audio* ca = ca_of(inst);
if (ca && ca->running) return 0;
@ -336,6 +338,8 @@ int call_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1;
}
if (listen(ca->listen_fd, 5) < 0) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
uint16_t bound_port = socket_get_bound_port(ca->listen_fd);
if (!bound_port) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
ca->listen_sock_id = uasync_add_socket_t(ua, ca->listen_fd, ca_accept_callback, NULL, NULL, "call_listen", ca);
if (!ca->listen_sock_id) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
@ -345,10 +349,16 @@ int call_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
if (!ca->cleanup_timer) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: cleanup timer scheduling failed", CH_ID);
ca->running = 1;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio socket listening on %s:%d", CH_ID, bind_ip, port);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio socket listening on %s:%u", CH_ID, bind_ip, bound_port);
return 0;
}
uint16_t call_headless_get_port(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: get port without instance", CH_ID); return 0; }
struct call_audio* ca = ca_of(inst);
return ca && ca->running ? socket_get_bound_port(ca->listen_fd) : 0;
}
void call_headless_destroy(struct UTUN_INSTANCE* inst) {
struct call_audio* ca = ca_of(inst);
if (!ca || !ca->running) return;

2
src/call/call_headless.h

@ -31,6 +31,8 @@ extern "C" {
int call_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port);
void call_headless_destroy(struct UTUN_INSTANCE* inst);
// port=0 при init выбирает ОС; 0 для остановленной службы или при ошибке.
uint16_t call_headless_get_port(struct UTUN_INSTANCE* inst);
#ifdef __cplusplus
}

14
src/chat/chat_headless_control.c

@ -903,7 +903,9 @@ static void hc_cleanup(void* arg) {
int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port) {
if (!ua || !inst || !bind_ip || port <= 0 || port > 65535) return -1;
if (!ua || !inst || !bind_ip || port < 0 || port > 65535) {
DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: invalid init arguments port=%d", port); return -1;
}
struct headless_control* hc = hc_of(inst);
if (hc && hc->running) return 0;
@ -929,6 +931,8 @@ int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; return -1;
}
if (listen(hc->listen_fd, 5) < 0) { socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; return -1; }
uint16_t bound_port = socket_get_bound_port(hc->listen_fd);
if (!bound_port) { socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; return -1; }
hc->listen_sock_id = uasync_add_socket_t(ua, hc->listen_fd, accept_callback, NULL, NULL, "chatctl_listen", hc);
if (!hc->listen_sock_id) { socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; return -1; }
@ -941,10 +945,16 @@ int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
}
hc->running = 1;
DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: listening on %s:%d", bind_ip, port);
DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: listening on %s:%u", bind_ip, bound_port);
return 0;
}
uint16_t chat_headless_control_get_port(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: get port without instance"); return 0; }
struct headless_control* hc = hc_of(inst);
return hc && hc->running ? socket_get_bound_port(hc->listen_fd) : 0;
}
void chat_headless_control_destroy(struct UTUN_INSTANCE* inst) {
struct headless_control* hc = hc_of(inst);
if (!hc) return;

2
src/chat/chat_headless_control.h

@ -23,5 +23,7 @@ struct UTUN_INSTANCE;
int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port);
void chat_headless_control_destroy(struct UTUN_INSTANCE* inst);
// port=0 при init выбирает ОС; getter возвращает 0 для остановленной службы или при ошибке.
uint16_t chat_headless_control_get_port(struct UTUN_INSTANCE* inst);
#endif /* CHAT_HEADLESS_CONTROL_H */

14
src/radio/radio_headless.c

@ -446,7 +446,9 @@ static void rh_cleanup(void* arg) {
int radio_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port) {
if (!ua || !inst || !bind_ip || port <= 0 || port > 65535) return -1;
if (!ua || !inst || !bind_ip || port < 0 || port > 65535) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "%s: invalid init arguments port=%d", RH_ID, port); return -1;
}
struct radio_headless* rh = rh_of(inst);
if (rh && rh->running) return 0;
@ -470,6 +472,8 @@ int radio_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
socket_close_wrapper(rh->listen_fd); rh->listen_fd = SOCKET_INVALID; return -1;
}
if (listen(rh->listen_fd, 5) < 0) { socket_close_wrapper(rh->listen_fd); rh->listen_fd = SOCKET_INVALID; return -1; }
uint16_t bound_port = socket_get_bound_port(rh->listen_fd);
if (!bound_port) { socket_close_wrapper(rh->listen_fd); rh->listen_fd = SOCKET_INVALID; return -1; }
rh->listen_sock_id = uasync_add_socket_t(ua, rh->listen_fd, rh_accept_callback, NULL, NULL, "radio_listen", rh);
if (!rh->listen_sock_id) { socket_close_wrapper(rh->listen_fd); rh->listen_fd = SOCKET_INVALID; return -1; }
@ -480,10 +484,16 @@ int radio_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
rh->mix_timer = uasync_set_timeout(ua, RH_MIX_TIMER_TB, rh, rh_mix_tick, "rh_mix");
rh->running = 1;
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: audio socket listening on %s:%d", RH_ID, bind_ip, port);
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: audio socket listening on %s:%u", RH_ID, bind_ip, bound_port);
return 0;
}
uint16_t radio_headless_get_port(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "%s: get port without instance", RH_ID); return 0; }
struct radio_headless* rh = rh_of(inst);
return rh && rh->running ? socket_get_bound_port(rh->listen_fd) : 0;
}
void radio_headless_destroy(struct UTUN_INSTANCE* inst) {
struct radio_headless* rh = rh_of(inst);
if (!rh || !rh->running) return;

2
src/radio/radio_headless.h

@ -36,6 +36,8 @@ extern "C" {
int radio_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port);
void radio_headless_destroy(struct UTUN_INSTANCE* inst);
// port=0 при init выбирает ОС; 0 для остановленной службы или при ошибке.
uint16_t radio_headless_get_port(struct UTUN_INSTANCE* inst);
#ifdef __cplusplus
}

11
src/transport_layer/stcp_server.c

@ -344,16 +344,7 @@ struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
uint16_t stcp_server_get_port(const struct stcp_server *srv) {
if (!srv) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: invalid server"); return 0; }
struct sockaddr_storage address;
socklen_t size = sizeof(address);
if (getsockname(srv->listen_sock, (struct sockaddr *)&address, &size) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: getsockname failed err=%d", socket_get_error());
return 0;
}
if (address.ss_family == AF_INET) return ntohs(((struct sockaddr_in *)&address)->sin_port);
if (address.ss_family == AF_INET6) return ntohs(((struct sockaddr_in6 *)&address)->sin6_port);
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server_get_port: unexpected family=%d", address.ss_family);
return 0;
return socket_get_bound_port(srv->listen_sock);
}
void stcp_server_destroy(struct stcp_server *srv) {

31
tests/test_call_headless.c

@ -30,6 +30,7 @@
#include "../lib/socket_compat.h"
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include "test_net.h"
#include <sqlite3.h>
#define OPENSSL_API_COMPAT 0x10100000L
@ -91,7 +92,6 @@ struct hs_shared {
uint8_t ch_x_pub[32], ch_ed_pub[32], ch_ed_priv[32], ch_sig[64];
uint64_t nid[2];
uint8_t x_pub[2][32], x_priv[2][32], ed_pub[2][32];
int port[2]; /* etcp-порты */
};
static struct hs_shared g_sh;
@ -130,13 +130,12 @@ static int wf(const char* p, const char* f, ...) {
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
static void fill_shared(int base_port) {
static void fill_shared(void) {
memset(&g_sh, 0, sizeof(g_sh));
for (int i = 0; i < 2; i++) {
gen_x25519(g_sh.x_pub[i], g_sh.x_priv[i]);
sc_derive_ed25519_pubkey(g_sh.x_priv[i], g_sh.ed_pub[i]);
g_sh.nid[i] = sc_derive_node_id_from_pubkey(g_sh.x_pub[i]);
g_sh.port[i] = base_port + i * 1000;
}
{ uint8_t ch_x_priv[32]; gen_x25519(g_sh.ch_x_pub, ch_x_priv); }
gen_ed25519(g_sh.ch_ed_pub, g_sh.ch_ed_priv);
@ -164,17 +163,16 @@ static void write_configs(const char* dir) {
if (i == IDX_B) {
char apub[65]; to_hex(g_sh.x_pub[IDX_A], 32, apub);
snprintf(client, sizeof(client),
"[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n",
apub, g_sh.port[IDX_A]);
"[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:0\n", apub);
}
wf(path,
"[global]\ntun_ip=10.97.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s/db%s\n"
"my_private_key=%s\nmy_public_key=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
"[server: s1]\naddr=127.0.0.1:0\ntype=public\n"
"%s"
"[chatserver]\nstorage_autoload=0\n[allowed_keys]\nallow_all=1\n",
i, i, dir, i == IDX_A ? "a" : "b",
priv, pub, g_sh.port[i], client);
priv, pub, client);
}
}
@ -312,12 +310,12 @@ static void hs_tick(void* arg) {
case P_SETUP:
do_setup(A, IDX_A);
do_setup(B, IDX_B);
if (chat_headless_control_init(A->ua, A, "127.0.0.1", 9700 + (int)(getpid() % 100)) != 0
|| chat_headless_control_init(B->ua, B, "127.0.0.1", 9800 + (int)(getpid() % 100)) != 0) {
if (chat_headless_control_init(A->ua, A, "127.0.0.1", 0) != 0
|| chat_headless_control_init(B->ua, B, "127.0.0.1", 0) != 0) {
fprintf(stderr, "headless control init failed\n"); t->result = 2; break;
}
if (call_headless_init(A->ua, A, "127.0.0.1", 9710 + (int)(getpid() % 100)) != 0
|| call_headless_init(B->ua, B, "127.0.0.1", 9810 + (int)(getpid() % 100)) != 0) {
if (call_headless_init(A->ua, A, "127.0.0.1", 0) != 0
|| call_headless_init(B->ua, B, "127.0.0.1", 0) != 0) {
fprintf(stderr, "call headless audio init failed\n"); t->result = 2; break;
}
t->phase = P_WAIT_BGP;
@ -326,10 +324,10 @@ static void hs_tick(void* arg) {
if (bgp_has(A, nb) && bgp_has(B, na)) t->phase = P_CONNECT_CLIENTS;
break;
case P_CONNECT_CLIENTS:
if (hs_connect(t, &t->ctl[IDX_A], 9700 + (int)(getpid() % 100)) != 0
|| hs_connect(t, &t->ctl[IDX_B], 9800 + (int)(getpid() % 100)) != 0
|| hs_connect(t, &t->aud[IDX_A], 9710 + (int)(getpid() % 100)) != 0
|| hs_connect(t, &t->aud[IDX_B], 9810 + (int)(getpid() % 100)) != 0) {
if (hs_connect(t, &t->ctl[IDX_A], chat_headless_control_get_port(A)) != 0
|| hs_connect(t, &t->ctl[IDX_B], chat_headless_control_get_port(B)) != 0
|| hs_connect(t, &t->aud[IDX_A], call_headless_get_port(A)) != 0
|| hs_connect(t, &t->aud[IDX_B], call_headless_get_port(B)) != 0) {
fprintf(stderr, "client connect failed\n"); t->result = 2; break;
}
t->phase = P_SUBSCRIBE;
@ -460,7 +458,7 @@ int main(int argc, char** argv) {
if (test_mkdtemp(dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
test_setenv("UTUN_TEST_DIR", dir, 1);
fill_shared(56000 + (getpid() % 2000));
fill_shared();
write_configs(dir);
printf("=== test_call_headless ===\n"); fflush(stdout);
@ -486,6 +484,7 @@ int main(int argc, char** argv) {
uasync_destroy(ua, 0);
return 1;
}
test_etcp_set_peer(t.inst[IDX_B], "to_a", t.inst[IDX_A], "s1");
utun_instance_init(t.inst[IDX_A]);
utun_instance_init(t.inst[IDX_B]);

9
tests/test_net.h

@ -10,12 +10,9 @@
// Читаем порт уже открытого сокета; владение fd и резервирование порта сохраняются.
static inline uint16_t test_bound_port(socket_t fd) {
struct sockaddr_storage address; socklen_t size = sizeof(address);
if (getsockname(fd, (struct sockaddr*)&address, &size) != 0 || !ss_get_port(&address)) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "test fixture: cannot read bound port fd=%d error=%d", (int)fd, socket_get_error());
abort();
}
return ss_get_port(&address);
uint16_t port = socket_get_bound_port(fd);
if (!port) abort();
return port;
}
static inline struct ETCP_SOCKET* test_etcp_bound_socket(struct UTUN_INSTANCE* inst, const char* name) {

30
tests/test_radio_headless.c

@ -28,6 +28,7 @@
#include "../lib/socket_compat.h"
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include "test_net.h"
#include <sqlite3.h>
#define OPENSSL_API_COMPAT 0x10100000L
@ -84,7 +85,6 @@ struct hr_shared {
uint8_t ch_x_pub[32], ch_ed_pub[32], ch_ed_priv[32], ch_sig[64];
uint64_t nid[2];
uint8_t x_pub[2][32], x_priv[2][32], ed_pub[2][32];
int port[2];
};
static struct hr_shared g_sh;
@ -121,13 +121,12 @@ static int wf(const char* p, const char* f, ...) {
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
static void fill_shared(int base_port) {
static void fill_shared(void) {
memset(&g_sh, 0, sizeof(g_sh));
for (int i = 0; i < 2; i++) {
gen_x25519(g_sh.x_pub[i], g_sh.x_priv[i]);
sc_derive_ed25519_pubkey(g_sh.x_priv[i], g_sh.ed_pub[i]);
g_sh.nid[i] = sc_derive_node_id_from_pubkey(g_sh.x_pub[i]);
g_sh.port[i] = base_port + i * 1000;
}
{ uint8_t ch_x_priv[32]; gen_x25519(g_sh.ch_x_pub, ch_x_priv); }
gen_ed25519(g_sh.ch_ed_pub, g_sh.ch_ed_priv);
@ -154,16 +153,16 @@ static void write_configs(const char* dir) {
char client[512] = "";
if (i == IDX_B) {
char apub[65]; to_hex(g_sh.x_pub[IDX_A], 32, apub);
snprintf(client, sizeof(client), "[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n", apub, g_sh.port[IDX_A]);
snprintf(client, sizeof(client), "[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:0\n", apub);
}
wf(path,
"[global]\ntun_ip=10.99.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s/db%s\n"
"my_private_key=%s\nmy_public_key=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
"[server: s1]\naddr=127.0.0.1:0\ntype=public\n"
"%s"
"[chatserver]\nstorage_autoload=0\n[allowed_keys]\nallow_all=1\n",
i, i, dir, i == IDX_A ? "a" : "b",
priv, pub, g_sh.port[i], client);
priv, pub, client);
}
}
@ -307,12 +306,12 @@ static void hr_tick(void* arg) {
case P_SETUP:
do_setup(A, IDX_A);
do_setup(B, IDX_B);
if (chat_headless_control_init(A->ua, A, "127.0.0.1", 9700 + (int)(getpid() % 100)) != 0
|| chat_headless_control_init(B->ua, B, "127.0.0.1", 9800 + (int)(getpid() % 100)) != 0) {
if (chat_headless_control_init(A->ua, A, "127.0.0.1", 0) != 0
|| chat_headless_control_init(B->ua, B, "127.0.0.1", 0) != 0) {
fprintf(stderr, "headless control init failed\n"); t->result = 2; break;
}
if (radio_headless_init(A->ua, A, "127.0.0.1", 9710 + (int)(getpid() % 100)) != 0
|| radio_headless_init(B->ua, B, "127.0.0.1", 9810 + (int)(getpid() % 100)) != 0) {
if (radio_headless_init(A->ua, A, "127.0.0.1", 0) != 0
|| radio_headless_init(B->ua, B, "127.0.0.1", 0) != 0) {
fprintf(stderr, "radio headless audio init failed\n"); t->result = 2; break;
}
t->phase = P_WAIT_BGP;
@ -321,10 +320,10 @@ static void hr_tick(void* arg) {
if (bgp_has(A, nb) && bgp_has(B, na)) t->phase = P_CONNECT_CLIENTS;
break;
case P_CONNECT_CLIENTS:
if (hr_connect(t, &t->ctl[IDX_A], 9700 + (int)(getpid() % 100)) != 0
|| hr_connect(t, &t->ctl[IDX_B], 9800 + (int)(getpid() % 100)) != 0
|| hr_connect(t, &t->aud[IDX_A], 9710 + (int)(getpid() % 100)) != 0
|| hr_connect(t, &t->aud[IDX_B], 9810 + (int)(getpid() % 100)) != 0) {
if (hr_connect(t, &t->ctl[IDX_A], chat_headless_control_get_port(A)) != 0
|| hr_connect(t, &t->ctl[IDX_B], chat_headless_control_get_port(B)) != 0
|| hr_connect(t, &t->aud[IDX_A], radio_headless_get_port(A)) != 0
|| hr_connect(t, &t->aud[IDX_B], radio_headless_get_port(B)) != 0) {
fprintf(stderr, "client connect failed\n"); t->result = 2; break;
}
t->phase = P_RADIO_ON;
@ -419,7 +418,7 @@ int main(int argc, char** argv) {
if (test_mkdtemp(dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
test_setenv("UTUN_TEST_DIR", dir, 1);
fill_shared(59000 + (getpid() % 2000));
fill_shared();
write_configs(dir);
printf("=== test_radio_headless ===\n"); fflush(stdout);
@ -445,6 +444,7 @@ int main(int argc, char** argv) {
uasync_destroy(ua, 0);
return 1;
}
test_etcp_set_peer(t.inst[IDX_B], "to_a", t.inst[IDX_A], "s1");
utun_instance_init(t.inst[IDX_A]);
utun_instance_init(t.inst[IDX_B]);

Loading…
Cancel
Save