commit c86fbcd84d03494d9288500e642ba3a7c93dfac1 from: Rohan Verma date: Thu Jul 2 11:49:35 2026 UTC chmod 700 milna exec, room cap, no nagle commit - 734e5440a8de4d4c26514b2f8405005037c773d9 commit + c86fbcd84d03494d9288500e642ba3a7c93dfac1 blob - 83651f6ccabe27ae4d422ff6b32a7d59531b64a3 blob + c41631176ccef8a0e007566bc6ae0bbd5b4386ab --- src/milna.h +++ src/milna.h @@ -1,7 +1,7 @@ #ifndef MILNA_H #define MILNA_H -#define MILNA_VERSION "1.7" +#define MILNA_VERSION "1.8" #include typedef struct { blob - b844cd5990ba0ca4798943ad8c6e2b2411ce1ba2 blob + 01fbc553eb4f576a0ac8584701a4d1142bdf174b --- src/net.c +++ src/net.c @@ -15,6 +15,7 @@ static void net_os_init(void) { #else #include #include +#include #include #include #include @@ -81,6 +82,7 @@ static void json_escape(const char *in, char *out, siz out[o] = 0; } +/* ---- peer table (slot-indexed; roster + crypto keys) ---- */ static struct peer { int used; char id[32], name[32]; @@ -119,6 +121,7 @@ static struct { static net_video_fn v_handler; static void *v_user; +/* ---- helpers ---- */ static void tcp_line(const char *line) { /* send one control line */ pthread_mutex_lock(&N.mu); if (N.tcp >= 0) { @@ -160,6 +163,8 @@ static void net_sink(const uint8_t *opus, int len, voi udp_send_media(2, 0, opus, len); } +/* screenshare/system audio sink: wire type 4 (music stream, separate from + * voice type 2). uses its own sequence so it doesn't disturb voice FEC. */ static void net_share_sink(const uint8_t *opus, int len, void *user) { (void)user; udp_send_media(4, 0, opus, len); @@ -169,7 +174,6 @@ int net_send_video(const uint8_t *frame, int len, int int layer) { if (!N.udp_ok) return -1; if (layer < 0 || layer > 3) layer = 0; - /* separate frame sequences per (source,layer): chunks never interleave */ static uint16_t fseq_[2][4]; uint16_t fseq = ++fseq_[!!is_screen][layer]; int cnt = (len + (VCHUNK - 8) - 1) / (VCHUNK - 8); @@ -269,7 +273,8 @@ static void peer_add(const char *json) { pthread_mutex_unlock(&N.mu); send_my_key(id, pk); - + /* tell the newcomer my current mute/deafen state so their roster shows + * the right badge immediately (state msgs are otherwise only on change) */ if (my_muted || my_deaf) net_announce_state(my_muted, my_deaf); LOG("%s in slot %d", p->name, slot); } @@ -392,10 +397,18 @@ static void on_media(const uint8_t *pkt, int n) { int src = (pt[7] >> 4) & 1; /* 0=camera 1=screen */ int dlen = ptlen - 8; int idx = pt[2] | pt[3] << 8; + /* reassembly buffers (p->va[].buf) are freed by peer_del on the TCP + * thread. hold N.mu across the buffer ops and RE-CHECK p->used after + * locking, so a peer leaving mid-frame can't free the buffer out + * from under this memcpy (was a use-after-free / data race). the + * critical section is just a memcpy, so it stays cheap. */ + uint8_t *done_buf = NULL; int done_len = 0, done_key = 0; + pthread_mutex_lock(&N.mu); + if (!p->used) { pthread_mutex_unlock(&N.mu); return; } struct vasm *a = &p->va[src]; if (!a->buf) { a->buf = malloc(VFRAME_MAX); - if (!a->buf) return; + if (!a->buf) { pthread_mutex_unlock(&N.mu); return; } a->seq = -1; } if (fseq != a->seq) { @@ -407,18 +420,32 @@ static void on_media(const uint8_t *pkt, int n) { a->key = key; } long off = (long)idx * (VCHUNK - 8); - if (off + dlen > VFRAME_MAX) { a->seq = -1; return; } + if (off + dlen > VFRAME_MAX) { a->seq = -1; pthread_mutex_unlock(&N.mu); return; } memcpy(a->buf + off, pt + 8, dlen); a->got++; if (off + dlen > a->len) a->len = (int)(off + dlen); if (a->got == a->cnt && v_handler) { static int first = 1; if (first) { LOG("first video frame RECEIVED (slot %d, %d bytes)", slot, a->len); first = 0; } - v_handler(slot, a->buf, a->len, a->key, src, v_user); + /* copy the completed frame into a private buffer UNDER the lock, + * so peer_del freeing a->buf can't race the decode. decode the + * copy after unlocking (the decoder can be slow and must not + * hold the peer lock). */ + done_buf = malloc((size_t)a->len); + if (done_buf) { + memcpy(done_buf, a->buf, (size_t)a->len); + done_len = a->len; done_key = a->key; + } } + pthread_mutex_unlock(&N.mu); + if (done_buf) { + v_handler(slot, done_buf, done_len, done_key, src, v_user); + free(done_buf); + } } } +/* ---- threads ---- */ static void *tcp_main(void *arg) { (void)arg; char buf[8192]; int fill = 0; @@ -517,7 +544,6 @@ int net_join(const char *server, const char *room, con if (sodium_init() < 0) return -1; pthread_mutex_init(&N.mu, NULL); - /* parse host[:port]; default port 9292; tolerate old ws:// urls */ char host[128]; int port = 9292; const char *s = server; if (!strncmp(s, "ws://", 5)) s += 5; @@ -539,12 +565,24 @@ int net_join(const char *server, const char *room, con LOG("control connect failed"); sock_close(N.tcp); N.tcp = -1; freeaddrinfo(res); return -3; } + { int one = 1; + setsockopt(N.tcp, IPPROTO_TCP, TCP_NODELAY, + (const char *)&one, sizeof(one)); } memcpy(&N.raddr, res->ai_addr, sizeof(N.raddr)); N.raddr.sin_port = htons((uint16_t)port); freeaddrinfo(res); N.udp = socket(AF_INET, SOCK_DGRAM, 0); + if (N.udp >= 0) { + int tos = 0xB8; + setsockopt(N.udp, IPPROTO_IP, IP_TOS, + (const char *)&tos, sizeof(tos)); + int rcv = 1 << 20; /* 1 MiB */ + setsockopt(N.udp, SOL_SOCKET, SO_RCVBUF, + (const char *)&rcv, sizeof(rcv)); + } + /* identity + sender key for this session */ crypto_box_keypair(N.pk, N.sk); randombytes_buf(N.skey, sizeof(N.skey)); randombytes_buf(N.snbase, sizeof(N.snbase)); blob - 059854a1b2977e34382b3bc8fb35f4e147b4cfb8 blob + e9cb2be653e83b08633356e88ac51293627f7736 --- src/update.c +++ src/update.c @@ -28,8 +28,8 @@ static void *update_run_blocking_thunk(void *a); #define EXE "" #endif -#define VER_URL "https://mi.rsap.sh/milna/version" -#define BIN_BASE "https://mi.rsap.sh/milna/milna-" +#define VER_URL "https://mi.rsap.sh/version" +#define BIN_BASE "https://mi.rsap.sh/milna-" static char g_self[1024]; /* path to the running executable */ static char g_new[1024]; /* path to the downloaded update */ @@ -84,7 +84,6 @@ static void self_path(char *out, size_t cap) { #ifdef _WIN32 #include static int run_argv(char *const argv[]) { - /* _spawnvp on windows: no fork, no cocoa, safe. */ intptr_t r = _spawnvp(_P_WAIT, argv[0], (const char *const *)argv); return r == 0 ? 0 : -1; } @@ -115,7 +114,16 @@ static int fetch_version(char *buf, size_t cap) { return buf[0] ? 0 : -1; } +static int valid_ver(const char *s) { + int digits = 0; + for (const char *p = s; *p; p++) { + if (*p >= '0' && *p <= '9') digits++; + else if (*p != '.') return 0; /* only digits and dots allowed */ + } + return digits > 0 && strlen(s) < 24; +} static int newer(const char *remote, const char *local) { + if (!valid_ver(remote)) return 0; /* junk remote: never "newer" */ int ra, rb = 0, la, lb = 0; ra = atoi(remote); const char *rd = strchr(remote, '.'); if (rd) rb = atoi(rd + 1); @@ -145,8 +153,6 @@ void update_run_blocking(void) { char bp[1024]; int in_bundle = bundle_path(bp, sizeof(bp)); if (in_bundle) { - /* bundle update: fetch the whole zipped .app, not the bare binary. - * we stage it as g_new + ".zip" beside the app. */ snprintf(url, sizeof(url), "%smacos-app.zip", BIN_BASE); char zip[1100]; snprintf(zip, sizeof(zip), "%s.zip", g_new); char *dz[] = { (char*)"curl", (char*)"-fsSL", (char*)"--max-time", @@ -157,7 +163,6 @@ void update_run_blocking(void) { "milna-macos-app.zip on the vps?\n"); remove(zip); return; } - /* strip quarantine on the zip so the unpacked app isn't tainted */ char *xz[] = { (char*)"xattr", (char*)"-d", (char*)"com.apple.quarantine", zip, NULL }; run_argv(xz); @@ -207,34 +212,31 @@ void update_init(const char *local_version) { fclose(z); fprintf(stderr, "[milna] update: staged bundle zip found, " "launching swap helper for %s\n", bp); - /* write a helper script to /tmp */ - char hp[] = "/tmp/milna_update.sh"; - FILE *h = fopen(hp, "w"); + char hp[] = "/tmp/milna_update.XXXXXX"; + int hfd = mkstemp(hp); + FILE *h = (hfd >= 0) ? fdopen(hfd, "w") : NULL; if (h) { - /* $1 = our pid, then it waits for us to die, unzips the new - * app to a temp dir, swaps it into place, reopens. */ + fchmod(hfd, 0700); fprintf(h, "#!/bin/sh\n" - "pid=\"$1\"; app=\"%s\"; zip=\"%s\"\n" + "pid=\"$1\"; app=\"$2\"; zip=\"$3\"\n" "for i in $(seq 1 50); do kill -0 \"$pid\" 2>/dev/null || break; sleep 0.1; done\n" - "tmp=$(mktemp -d /tmp/milna.XXXXXX)\n" - "if ! /usr/bin/unzip -oq \"$zip\" -d \"$tmp\"; then exit 1; fi\n" + "tmp=$(mktemp -d /tmp/milna.XXXXXX) || exit 1\n" + "if ! /usr/bin/unzip -oq \"$zip\" -d \"$tmp\"; then rm -rf \"$tmp\"; exit 1; fi\n" "newapp=$(find \"$tmp\" -maxdepth 2 -name '*.app' -type d | head -1)\n" - "[ -n \"$newapp\" ] || exit 1\n" + "[ -n \"$newapp\" ] || { rm -rf \"$tmp\"; exit 1; }\n" "/usr/bin/xattr -cr \"$newapp\" 2>/dev/null\n" "rm -rf \"$app.old\"\n" "mv \"$app\" \"$app.old\" 2>/dev/null\n" - "if ! mv \"$newapp\" \"$app\"; then mv \"$app.old\" \"$app\"; exit 1; fi\n" + "if ! mv \"$newapp\" \"$app\"; then mv \"$app.old\" \"$app\"; rm -rf \"$tmp\"; exit 1; fi\n" "/usr/bin/codesign --force --deep --sign - \"$app\" 2>/dev/null\n" "rm -rf \"$app.old\" \"$tmp\" \"$zip\"\n" - "/usr/bin/open \"$app\"\n", - bp, zip); + "/usr/bin/open \"$app\"\n"); fclose(h); - chmod(hp, 0755); /* spawn detached and exit so the helper can replace us */ char pidbuf[16]; snprintf(pidbuf, sizeof(pidbuf), "%d", (int)getpid()); - char *hv[] = { (char*)"/bin/sh", hp, pidbuf, NULL }; + char *hv[] = { (char*)"/bin/sh", hp, pidbuf, bp, zip, NULL }; pid_t child = fork(); if (child == 0) { setsid(); @@ -271,17 +273,15 @@ void update_init(const char *local_version) { } else { fprintf(stderr, "[milna] update: promoted, relaunching\n"); #ifdef __APPLE__ - { char bundle[1024]; snprintf(bundle, sizeof(bundle), "%s", g_self); char *p = strstr(bundle, ".app/Contents/MacOS/"); if (p) { p[4] = 0; /* terminate right after ".app" */ - char cmd[1200]; - snprintf(cmd, sizeof(cmd), - "codesign --force --sign - '%s' 2>/dev/null", bundle); - int rc = system(cmd); + char *cs[] = { (char*)"codesign", (char*)"--force", + (char*)"--sign", (char*)"-", bundle, NULL }; + int rc = run_argv(cs); fprintf(stderr, "[milna] update: re-signed bundle " "(%s, rc=%d)\n", bundle, rc); } @@ -299,7 +299,6 @@ void update_init(const char *local_version) { } #ifdef __APPLE__ - void update_dispatch_bg(void); #endif @@ -319,6 +318,7 @@ void update_check_async(void) { } #ifndef __APPLE__ +/* pthread needs a void*(void*) signature */ static void *update_run_blocking_thunk(void *a) { (void)a; update_run_blocking(); return NULL; } blob - 113c1e75b294a6c5fc5f7a514c9f7e21b0ff0315 blob + a7836ecd0f19d6fe340ed999d6e5ba54801ac466 --- srv/relay.go +++ srv/relay.go @@ -9,6 +9,7 @@ import ( "net" "strconv" "sync" + "time" ) const port = ":9292" @@ -37,10 +38,10 @@ type member struct { pub string room string conn net.Conn - wmu sync.Mutex + wmu sync.Mutex // serializes writes to conn token [16]byte - udp *net.UDPAddr - want int + udp *net.UDPAddr // bound media address (nil until hello) + want int // simulcast: which video layer this member receives } func (m *member) send(v msg) { @@ -70,11 +71,18 @@ func (h *hub) join(m *member, room, name, pub string) h.rooms[room] = map[string]*member{} } + const maxRoom = 16 + if len(h.rooms[room]) >= maxRoom { + m.send(msg{Type: "full"}) + m.room = "" + return + } + // pick the lowest free slot (u8, room-scoped) used := map[int]bool{} for _, o := range h.rooms[room] { used[o.slot] = true } - for s := 0; s < 256; s++ { + for s := 0; s < maxRoom; s++ { if !used[s] { m.slot = s break @@ -149,7 +157,7 @@ func (h *hub) udpLoop() { } h.mu.Unlock() h.udp.WriteToUDP(buf[:n], addr) // confirm - case 2, 3, 4: + case 2, 3, 4: layer := -1 if buf[1] == 3 { layer = int(buf[3]>>1) & 3 @@ -187,7 +195,9 @@ func (h *hub) serve(conn net.Conn) { sc := bufio.NewScanner(conn) sc.Buffer(make([]byte, 64*1024), 64*1024) + conn.SetReadDeadline(time.Now().Add(120 * time.Second)) for sc.Scan() { + conn.SetReadDeadline(time.Now().Add(120 * time.Second)) var v msg if json.Unmarshal(sc.Bytes(), &v) != nil { continue