Rohan Verma



Commit Diff


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 <stdint.h>
 
 typedef struct {
blob - b844cd5990ba0ca4798943ad8c6e2b2411ce1ba2
blob + 01fbc553eb4f576a0ac8584701a4d1142bdf174b
--- src/net.c
+++ src/net.c
@@ -15,6 +15,7 @@ static void net_os_init(void) {
 #else
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <netdb.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -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 <process.h>
 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