Browse Source

lots of work completed towards getting TCP connections working. Sockets are now created when requested by CX, data can be sent. not received yet. This specific commit wont' build because I'm trying to import dumpIP but can't manage to. just remove from dSockets.js and it'll work. pushing anyways because I hvaen't pushed in too long and I'll die if I lose all this

oscar 1 month ago
parent
commit
71c1548653
18 changed files with 1411 additions and 1009 deletions
  1. 1 4
      .well-known/manifest.webmanifest
  2. 308 302
      assets/cx_esm.js
  3. 272 153
      cheerpOS.js
  4. 308 302
      cx.js
  5. 163 163
      cxcore.js
  6. BIN
      cxcore.wasm
  7. 314 0
      dSockets.js
  8. 1 0
      nginx.conf
  9. 1 1
      package.json
  10. 15 4
      src/lib/TCPProxy.js
  11. 9 5
      src/lib/TCPSocketClient.js
  12. 0 68
      src/lib/WebVM.svelte
  13. 2 3
      src/lib/network.js
  14. 10 0
      src/lib/streams.js
  15. 0 2
      svelte.config.js
  16. 0 0
      tun/ipstack.js
  17. BIN
      tun/ipstack.wasm
  18. 7 2
      tun/tailscale_tun.js

+ 1 - 4
.well-known/manifest.webmanifest

@@ -18,9 +18,6 @@
   "isolated_storage": true,
   "permissions_policy": {
     "cross-origin-isolated": ["self"],
-    "direct-sockets": ["self"],
-    "controlled-frame": ["self"],
-    "window-management": ["self"],
-    "all-screens-capture": ["self"]
+    "direct-sockets": ["self"]
   }
 }

File diff suppressed because it is too large
+ 308 - 302
assets/cx_esm.js


+ 272 - 153
cheerpOS.js

@@ -862,6 +862,11 @@ function webReadAsyncLoop(fileData, fileOffset, buf, off, len, fileRef, startChu
 
 function webReadAsyncEnd(fileData, fileOffset, buf, off, len, cb)
 {
+	if(buf.length == 0)
+	{
+		// Defend against buffer invalidation
+		return cb(/*EFAULT*/-14);
+	}
 	// All chunks are now loaded
 	var chunkSize = fileData.parent.chunkSize;
 	var curChunk = fileOffset / chunkSize | 0;
@@ -2382,7 +2387,7 @@ function cheerpOSClose(fds, fd, cb)
 		cb();
 }
 
-function socketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
+function httpSocketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
 {
 	assert(len != 0);
 	let cnt = off;
@@ -2390,11 +2395,16 @@ function socketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
 	if(fileData.currData === null){
 		return cb(0);
 	}
-	return socketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb);
+	return httpSocketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb);
 }
 
-function socketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb)
+function httpSocketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb)
 {
+	if(buf.length == 0)
+	{
+		// Defend against buffer invalidation
+		return cb(/*EFAULT*/-14);
+	}
 	while(1){
 		if(cnt === off+len){
 			break;
@@ -2407,7 +2417,7 @@ function socketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb)
 				if(cnt>off)
 					return cb(cnt-off);
 				fileData.blockedThread = function(){
-					return socketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb);
+					return httpSocketReadLoop(fileData, fileOffset, buf, off, len, flags, cnt, cb);
 				};
 				return;
 			}
@@ -2437,6 +2447,7 @@ function cheerpNetReadFetchOS(reader,fileData)
 {
 	reader.read()
 		.then(function(result){
+			console.log(result);
 			if(result.done)
 			{
 				fileData.chunks.push(null);
@@ -2456,7 +2467,7 @@ function cheerpNetReadFetchOS(reader,fileData)
 			{
 				var blockedThread=fileData.blockedThread;
 				fileData.blockedThread=null;
-				return blockedThread();
+				blockedThread();
 			}
 			if(!result.done)
 			{
@@ -2596,21 +2607,90 @@ function httpSocketWriteAsync(fileData, fileOffset, buf, off, len, cb)
 	return cb(cnt);
 }
 
-function socketWriteAsync(fileData, fileOffset, buf, off, len, cb)
+function httpSocketConnect(fileData, ipAddr, ipPort)
 {
-	var ret = fileData.data.send(buf.subarray(off, off+len));
-	if(ret == 0)
-		ret = len;
-	return cb(ret);
+	// Pretend that the connection succeeds, we'll try to parse an HTTP session in the write handler.
+	return 0;
 }
 
-function udpSocketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
+async function tcpSocketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
 {
-	debugger;
+	assert(len != 0);
+	while(true)
+	{
+		var ret = fileData.data.recv(buf, off, len);
+		if (ret != cjTailscaleSocket.Eagain)
+			return cb(ret);
+		await fileData.net.canRead(fileData);
+	}
 }
-function udpSocketWriteAsync(fileData, fileOffset, buf, off, len, cb)
+
+function tcpSocketReadAvailable(fileData)
 {
-	debugger;
+	return fileData.data.readAvailable();
+}
+
+function tcpSocketListen(fileData)
+{
+	var ret = fileData.data.listen();
+	return ret == 0? 0 : -1;
+}
+
+function tcpSocketConnect(fileData, ipAddr, ipPort)
+{
+	fileData.data.bind(0);
+	var ret = fileData.data.connect(cjTailscaleParseIp(ipAddr), ipPort);
+	return ret == 0? 0 : -1;
+}
+
+function tcpSocketAccept(fileData)
+{
+	return fileData.data.accept();
+}
+
+function socketBind(fileData, port)
+{
+	return fileData.data.bind(port);
+}
+
+function tcpSocketShutdown(fileData, how)
+{
+	switch(how)
+	{
+		case 0:
+			ret = fileData.data.shutdownRx();
+			break;
+		case 1:
+			ret = fileData.data.shutdownTx();
+			break;
+		case 2:
+			var ret1 = fileData.data.shutdownRx();
+			var ret2 = fileData.data.shutdownTx();
+			ret = ret1 != 0? ret1 : ret2;
+			break;
+	}
+	return ret;
+}
+
+async function tcpSocketWriteAsync(fileData, fileOffset, buf, off, len, cb)
+{
+	while(true)
+	{
+		var ret = fileData.data.send(buf, off, len);
+		if (ret != cjTailscaleSocket.Eagain)
+			return cb(ret);
+		await fileData.net.canWrite(fileData);
+	}
+}
+
+function socketRecv(fileData, fileOffset, buf, off, len, addrInfo)
+{
+	return fileData.data.recv(fileOffset, buf, off, len, addrInfo);
+}
+
+function socketSendTo(fileData, buf, addr, port)
+{
+	return fileData.data.sendto(buf, cjTailscaleParseIp(addr), port);
 }
 
 function socketClose(fileData, cb)
@@ -2618,13 +2698,45 @@ function socketClose(fileData, cb)
 	if (fileData.data)
 	{
 		fileData.data.close();
+		fileData.data.delete();
 		fileData.data = null;
 	}
 	cb();
 }
 
+async function socketCanRead(fileData)
+{
+	if (!fileData.incomingPromise)
+	{
+		fileData.incomingPromise = fileData.data.waitIncoming().then(ret => {
+			fileData.incomingPromise = null;
+			return ret;
+		});
+	}
+	var ret = await fileData.incomingPromise;
+	return ret;
+}
+
+async function socketCanWrite(fileData)
+{
+	if (!fileData.outgoingPromise)
+	{
+		fileData.outgoingPromise = fileData.data.waitOutgoing().then(ret => {
+			fileData.outgoingPromise = null;
+			return ret;
+		});
+	}
+	var ret = await fileData.outgoingPromise;
+	return ret;
+}
+
 function internalSocketReadAsync(fileData, fileOffset, buf, off, len, flags, cb)
 {
+	if(buf.length == 0)
+	{
+		// Defend against buffer invalidation
+		return cb(/*EFAULT*/-14);
+	}
 	if(fileData.chunks.length == 0)
 	{
 		if(fileData.flags & CheerpJFileData.O_NONBLOCK)
@@ -2653,70 +2765,106 @@ function internalSocketWriteAsync(fileData, fileOffset, buf, off, len, cb)
 	if(len > 0)
 	{
 		var socket = fileData.data;
-		var peer = socket.peerSocket;
 		var data = new Uint8Array(buf.subarray(off, off + len));
-		peer.recvCb(data);
+		socket.peerFileData.chunks.push(data);
+		if (socket.peerFileData.blockedThread)
+		{
+			var thread = socket.peerFileData.blockedThread;
+			socket.peerFileData.blockedThread = null;
+			thread();
+		}
 	}
 	return cb(len);
 }
 
-function internalSocketClose(fileData, cb)
+async function internalSocketCanRead(fileData)
 {
-	debugger;
+	if(fileData.chunks.length != 0 || fileData.data.acceptQueue.length != 0)
+	{
+		return 0;
+	}
+	let { promise, resolve, reject } = Promise.withResolvers();
+	fileData.blockedThread = resolve;
+	await promise;
+	return 0;
 }
 
-function internalSocketRecv(recvCb)
+function internalSocketClose(fileData, cb)
 {
-	this.recvCb = recvCb;
+	debugger;
 }
 
-function internalSocketConnect(addr, port, cb)
+function internalSocketConnect(fileData, addr, port)
 {
-	var boundSocket = InternalSocket.boundSockets[port];
-	if(boundSocket)
+	var peer = InternalSocket.boundSockets[port];
+	if(peer && peer.data.listening)
 	{
 		var newSocket = new InternalSocket();
-		this.peerSocket = newSocket;
-		newSocket.peerSocket = this;
-		boundSocket.listenCb(newSocket, null, null, 0);
-		return cb(0);
+		newSocket.peerFileData = fileData;
+		peer.data.acceptQueue.push({socket:newSocket, addr:addr, port:port});
+		if (peer.blockedThread)
+		{
+			var thread = peer.blockedThread;
+			peer.blockedThread = null;
+			thread();
+		}
+		return 0;
 	}
-	return cb(-1);
+	return -1;
+}
+function internalSocketAccept(fileData)
+{
+	if (fileData.data.acceptQueue.length == 0)
+		return null;
+	var s = fileData.data.acceptQueue.shift();
+	return s;
 }
 
-function internalSocketBind(port)
+function internalSocketBind(fileData, port)
 {
 	if(port == 0)
 	{
 		// Used to assign a port, nothing to do
 		return 0;
 	}
-	InternalSocket.boundSockets[port] = this;
+	InternalSocket.boundSockets[port] = fileData;
 	return 0;
 }
 
-function internalSocketListen(listenCb)
+function internalSocketListen(fileData)
 {
-	this.listenCb = listenCb;
+	fileData.data.listening = true;
 }
 
 function InternalSocket()
 {
-	this.recvCb = null;
-	this.listenCb = null;
-	this.peerSocket = null;
+	this.peerFileData = null;
+	this.listening = false;
+	this.acceptQueue = [];
 }
 
-InternalSocket.prototype.recv = internalSocketRecv;
-InternalSocket.prototype.connect = internalSocketConnect;
-InternalSocket.prototype.bind = internalSocketBind;
-InternalSocket.prototype.listen = internalSocketListen;
 InternalSocket.boundSockets = {};
 
-var SocketInodeOps = { readAsync: socketReadAsync, writeAsync: socketWriteAsync, close: socketClose };
-var UdpSocketInodeOps = { readAsync: udpSocketReadAsync, writeAsync: udpSocketWriteAsync, close: socketClose };
-var httpSocketInodeOps = { readAsync: socketReadAsync, writeAsync: httpSocketWriteAsync, close: null };
+function netOpsUnimplemented()
+{
+	debugger;
+}
+function netOpsAlwaysReady()
+{
+	return Promise.resolve(0);
+}
+var TcpSocketInodeOps = { readAsync: tcpSocketReadAsync, writeAsync: tcpSocketWriteAsync, close: socketClose };
+var TcpSocketNetOps = { canWrite: socketCanWrite, canRead: socketCanRead, readAvailable: tcpSocketReadAvailable, recv: netOpsUnimplemented, sendto: netOpsUnimplemented, connect: tcpSocketConnect, listen: tcpSocketListen, accept: tcpSocketAccept, bind: socketBind, shutdown: tcpSocketShutdown };
+
+var UdpSocketInodeOps = { readAsync: netOpsUnimplemented, writeAsync: netOpsUnimplemented, close: socketClose };
+var UdpSocketNetOps = { canWrite: netOpsAlwaysReady, canRead: socketCanRead, readAvailable: netOpsUnimplemented, recv: socketRecv, sendto: socketSendTo, connect: netOpsUnimplemented, listen: netOpsUnimplemented, accept: netOpsUnimplemented, bind: socketBind, shutdown: null };
+
+
+var HttpSocketInodeOps = { readAsync: httpSocketReadAsync, writeAsync: httpSocketWriteAsync, close: null };
+var HttpSocketNetOps = { canWrite: netOpsAlwaysReady, canRead: netOpsUnimplemented, readAvailable: null, recv: netOpsUnimplemented, sendto: netOpsUnimplemented, connect: httpSocketConnect, listen: netOpsUnimplemented, accept: netOpsUnimplemented, bind: netOpsUnimplemented, shutdown: netOpsUnimplemented };
+
 var InternalSocketInodeOps = { readAsync: internalSocketReadAsync, writeAsync: internalSocketWriteAsync, close: internalSocketClose };
+var InternalSocketNetOps = { canWrite: netOpsAlwaysReady, canRead: internalSocketCanRead, readAvailable: null, recv: netOpsUnimplemented, sendto: netOpsUnimplemented, connect: internalSocketConnect, listen: internalSocketListen, accept: internalSocketAccept, bind: internalSocketBind, shutdown: netOpsUnimplemented };
 
 function cheerpOSSocketOpenInternal(fds, mode, socket)
 {
@@ -2725,15 +2873,13 @@ function cheerpOSSocketOpenInternal(fds, mode, socket)
 	if(mode == 2)
 	{
 		fileData.mount = InternalSocketInodeOps;
+		fileData.net = InternalSocketNetOps;
 		fileData.data = socket? socket : new InternalSocket();
-		fileData.data.recv((data) => {
-			fileData.chunks.push(data);
-			if(fileData.blockedThread){
-				var blockedThread=fileData.blockedThread;
-				fileData.blockedThread=null;
-				blockedThread();
-			}
-		});
+		if (fileData.data.peerFileData)
+		{
+			var peer = fileData.data.peerFileData;
+			peer.data.peerFileData = fileData;
+		}
 	}
 	else
 	{
@@ -2741,7 +2887,8 @@ function cheerpOSSocketOpenInternal(fds, mode, socket)
 		{
 			if (mode != 0)
 				return -1;
-			fileData.mount = httpSocketInodeOps;
+			fileData.mount = HttpSocketInodeOps;
+			fileData.net = HttpSocketNetOps;
 			fileData.currRequest = {
 				headers: new Headers(),
 				url: "",
@@ -2759,30 +2906,14 @@ function cheerpOSSocketOpenInternal(fds, mode, socket)
 			if (mode == 1)
 			{
 				fileData.mount = UdpSocketInodeOps;
-				fileData.data = socket? socket : cjTailscaleUdpSocket();
-				fileData.data.recv((data, addr, port) => {
-					fileData.chunks.push({data,addr,port});
-					if(fileData.blockedThread){
-						var blockedThread=fileData.blockedThread;
-						fileData.blockedThread=null;
-						blockedThread();
-					}
-				});
+				fileData.net = UdpSocketNetOps;
+				fileData.data = socket? socket : new cjTailscaleUdpSocket();
 			}
 			else
 			{
-				fileData.mount = SocketInodeOps;
-				fileData.data = socket? socket : cjTailscaleSocket();
-				fileData.data.recv((data) => {
-					// The data is borrowed and we can't use it after this callback
-					var copy = data == null? null : new Uint8Array(data);
-					fileData.chunks.push(copy);
-					if(fileData.blockedThread){
-						var blockedThread=fileData.blockedThread;
-						fileData.blockedThread=null;
-						blockedThread();
-					}
-				});
+				fileData.mount = TcpSocketInodeOps;
+				fileData.net = TcpSocketNetOps;
+				fileData.data = socket? socket : new cjTailscaleSocket();
 			}
 		}
 	}
@@ -2807,27 +2938,12 @@ function cheerpOSSocketShutdown(fds, fd, how, cb)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
 	var ret = 0;
-	if (fileData.data)
-	{
-		switch(how)
-		{
-			case 0:
-				ret = fileData.data.shutdownRx();
-				break;
-			case 1:
-				ret = fileData.data.shutdownTx();
-				break;
-			case 2:
-				var ret1 = fileData.data.shutdownRx();
-				var ret2 = fileData.data.shutdownTx();
-				ret = ret1 != 0? ret1 : ret2;
-				break;
-		}
-	}
-	return cb(ret);
+	if (!fileData.net || !fileData.net.shutdown)
+		return cb(-1);
+	return cb(fileData.net.shutdown(fileData, how));
 }
 
-function cheerpOSPoll(fds, fd, timeout, cb)
+async function cheerpOSPoll(fds, fd, timeout, cb)
 {
 	if(fd < 0)
 		return cb(-1);
@@ -2835,23 +2951,29 @@ function cheerpOSPoll(fds, fd, timeout, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData = fileDesc.fileData;
-	if(fileData.currData || fileData.chunks.length > 0)
-		return cb(1);
+	var canReadPromise;
+	if (fileData.net)
+		canReadPromise = fileData.net.canRead(fileData);
+	else
+		canReadPromise = Promise.resolve(0);
+	var promises = [];
 	var timeoutId = 0;
 	if(timeout >= 0)
 	{
-		var timeoutCb = () => {
-			fileData.blockedThread = null;
-			return cb(0);
-		};
-		timeoutId = setTimeout(timeoutCb, timeout);
+		const timoutPromise = new Promise(resolve => {
+			timeoutId = setTimeout(resolve, timeout);
+		});
+		promises.push(timoutPromise.then(() => {
+			return 0;
+		}));
 	}
-	var pollCb = () => {
-		if(timeoutId)
+	promises.push(canReadPromise.then(() => {
+		if (timeoutId)
 			clearTimeout(timeoutId);
-		return cb(1);
-	};
-	fileData.blockedThread = pollCb;
+		fileData.incomingPromise = null;
+		return 1;
+	}));
+	return cb(await Promise.race(promises))
 }
 
 function cheerpOSReadAvailable(fds, fd, cb)
@@ -2863,6 +2985,10 @@ function cheerpOSReadAvailable(fds, fd, cb)
 		return cb(-1);
 	var tot = 0;
 	var data = fileDesc.fileData;
+	if (data.net && data.net.readAvailable)
+	{
+		return cb(data.net.readAvailable(data));
+	}
 	for(var i = 0; i < data.chunks.length; i++)
 	{
 		if(data.chunks[i])
@@ -2875,7 +3001,7 @@ function cheerpOSReadAvailable(fds, fd, cb)
 	return cb(tot);
 }
 
-function cheerpOSSocketConnect(fds, fd, ipAddr, ipPort, cb)
+async function cheerpOSSocketConnect(fds, fd, ipAddr, ipPort, cb)
 {
 	if(fd < 0)
 		return cb(-1);
@@ -2883,13 +3009,13 @@ function cheerpOSSocketConnect(fds, fd, ipAddr, ipPort, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
-	{
-		// Pretend that the connection succeeds, we'll try to parse an HTTP session in the write handler.
-		return cb(0);
-	}
-	fileData.data.bind(0);
-	fileData.data.connect(ipAddr != "unix" ? cjTailscaleParseIp(ipAddr) : "unix", ipPort, cb);
+	if (!fileData.net)
+		return cb(-1);
+	var ret = fileData.net.connect(fileData, ipAddr, ipPort);
+	if (ret != 0)
+		return cb(-1);
+	var ret = await fileData.net.canWrite(fileData);
+	return cb(ret);
 }
 
 function cheerpOSSocketListen(fds, fd, backlog, cb)
@@ -2900,20 +3026,12 @@ function cheerpOSSocketListen(fds, fd, backlog, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
+	if(!fileData.net)
 		return cb(-1);
-	return cb(fileData.data.listen((socket, addr, port, err) => {
-		assert(err == 0);
-		fileData.chunks.push({socket,addr,port});
-		if(fileData.blockedThread){
-			var blockedThread=fileData.blockedThread;
-			fileData.blockedThread=null;
-			blockedThread();
-		}
-	}));
+	return cb(fileData.net.listen(fileData));
 }
 
-function cheerpOSSocketAccept(fds, fd, cb)
+async function cheerpOSSocketAccept(fds, fd, cb)
 {
 	if(fd < 0)
 		return cb(-1);
@@ -2921,20 +3039,15 @@ function cheerpOSSocketAccept(fds, fd, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
+	if(!fileData.net)
 		return cb(-1);
-	var doRead = () => {
-		var first = fileData.chunks.shift();
-		var newFd = cheerpOSSocketOpenInternal(fds, (first.socket instanceof InternalSocket) ? 2 : 0, first.socket);
-		return cb({newFd,addr:first.addr,port:first.port});
-	};
-	if(fileData.chunks.length != 0)
-	{
-		doRead();
-		return;
+	var newSocket = fileData.net.accept(fileData);
+	while (newSocket == null) {
+		await fileData.net.canRead(fileData);
+		newSocket = fileData.net.accept(fileData);
 	}
-	fileData.blockedThread = doRead;
-	return;
+	var newFd = cheerpOSSocketOpenInternal(fds, (newSocket.socket instanceof InternalSocket) ? 2 : 0, newSocket.socket);
+	return cb({newFd,addr:newSocket.addr,port:newSocket.port});
 }
 
 function cheerpOSSocketBind(fds, fd, localPort, cb)
@@ -2945,12 +3058,12 @@ function cheerpOSSocketBind(fds, fd, localPort, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
+	if(!fileData.net)
 		return cb(-1);
-	return cb(fileData.data.bind(localPort));
+	return cb(fileData.net.bind(fileData, localPort));
 }
 
-function cheerpOSSocketSendTo(fds, fd, data, ipAddr, ipPort, cb)
+async function cheerpOSSocketSendTo(fds, fd, data, ipAddr, ipPort, cb)
 {
 	if(fd < 0)
 		return cb(-1);
@@ -2958,31 +3071,37 @@ function cheerpOSSocketSendTo(fds, fd, data, ipAddr, ipPort, cb)
 	if(fileDesc == null)
 		return cb(-1);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
+	if(!fileData.net)
 		return cb(-1);
-	return cb(fileData.data.sendto(data, cjTailscaleParseIp(ipAddr), ipPort));
+	var ret = fileData.net.sendto(fileData, data, ipAddr, ipPort);
+	while(ret == cjTailscaleUdpSocket.Eagain) {
+		ret = await fileData.net.canWrite(fileData);
+		if (ret < 0)
+			return cb(-1);
+		ret = fileData.data.sendto(fileData, data, ipAddr, ipPort);
+	}
+	return cb(ret);
 }
-function cheerpOSSocketRecv(fds, fd, cb)
+async function cheerpOSSocketRecv(fds, fd, buf, off, bufLen, cb)
 {
+	var ret = {length:-1,addr:0,port:0};
 	if(fd < 0)
-		return cb({data:null,addr:0,port:0});
+		return cb(ret);
 	var fileDesc=fds[fd];
 	if(fileDesc == null)
-		return cb({data:null,addr:0,port:0});
+		return cb(ret);
 	var fileData=fileDesc.fileData;
-	if(!fileData.data)
-		return cb({data:null,addr:0,port:0});
-	var doRead = () => {
-			var first = fileData.chunks.shift();
-			return cb(first);
-	};
-	if(fileData.chunks.length != 0)
+	if(!fileData.net)
+		return cb(ret);
+	ret.length = fileData.net.recv(fileData, buf, off, bufLen, ret);
+	while(ret.length == cjTailscaleUdpSocket.Eagain)
 	{
-		doRead();
-		return;
+		ret.length = await fileData.net.canRead(fileData);
+		if (ret.length < 0)
+			return cb(ret);
+		ret.length = fileData.net.recv(fileData, buf, off, bufLen, ret);
 	}
-	fileData.blockedThread = doRead;
-	return;
+	return cb(ret);
 }
 
 function cheerpOSResolveHost(hostName, cb)

File diff suppressed because it is too large
+ 308 - 302
cx.js


File diff suppressed because it is too large
+ 163 - 163
cxcore.js


BIN
cxcore.wasm


+ 314 - 0
dSockets.js

@@ -0,0 +1,314 @@
+import {dumpIP} from "./tun/tailscale_tun.js"
+
+export class DirectNetwork {
+  constructor() {}
+  tcpSocket(ip, port, options) {
+    return new TCPSocket(ip, port, options)
+  }
+}
+
+export class TCPSocketClient {
+  constructor(ip, port, options) {
+    this.localPort = null;
+    this.socket = null;
+    this.remoteAddress = null;
+    this.remotePort = null;
+    this.options = null;
+
+    this.readable = null;
+    this.writable = null;
+
+    const formatted_ip = dumpIP(ip);
+    this.socket = new TCPSocket(formatted_ip, port, options);
+    console.log(`Created new TCPSocket: ip=[${ip}], port=[${port}], options=[${options}]`);
+  }
+  async connect() {
+    try {
+      const {readable, writable} = await this.socket.opened;
+      if (readable && writable) {
+        this.readable = readable;
+        this.writable = writable;
+        console.log("writable, readable: ", writable, readable);
+        console.log("return 0");
+        return 0;
+      }
+      console.log("return 1");
+      return 1;
+    } catch (e) {
+      console.error(`TCPSocketClient failed to connect: `, e);
+      return 2
+    }
+  }
+  async send(data) {
+    const writer = this.writable.getWriter();
+    await writer.write(data);
+    writer.releaseLock();
+  }
+  async recv() {
+    const reader = this.readable.getReader();
+    while (true) {
+      const {value, done} = await reader.read();
+      if (done) {
+        break;
+      }
+      console.log("value in recv(): ", value);
+    }
+    reader.releaseLock();
+  }
+  // being xtra xtra safe but I think it'll cause issues to get reader and writer here again
+  async close() {
+    const writer = this.writable.getWriter();
+    const reader = this.readable.getReader();
+
+    await writer.close();
+    await reader.close();
+    await this.socket.close();
+  }
+  // async connect(timeoutMs = 5000) {
+  //   try {
+  //     let timeoutID;
+  //     // added timeout cause it seems to be standard
+  //     const timeoutPromise = new Promise((_, reject) => {
+  //       timeoutID = setTimeout(() => {
+  //         console.log(`Connection to ${this.remoteAddress}:${this.remotePort} timed out after ${timeoutMs}ms`);
+  //         reject(new Error("Connection timeout"));
+  //       }, timeoutMs);
+  //     });
+
+  //     this.socket = new TCPSocket(this.remoteAddress, this.remotePort, this.options);
+  //     // race between socket.opened and timeout
+  //     const openInfo = await Promise.race([this.socket.opened, timeoutPromise]);
+  //     clearTimeout(timeoutID);
+
+  //     this._readable = openInfo.readable;
+  //     this._writable = openInfo.writable;
+
+  //     if (this.onData) {
+  //       this._startReading();
+  //     }
+  //     if (this.onOpen) {
+  //       this.onOpen(openInfo);
+  //     }
+
+  //     this._resolveOpened(openInfo);
+  //     return openInfo;
+  //   } catch (e) {
+  //     this._rejectOpened(e);
+  //     this._rejectClosed(e);
+
+  //     if (this.onError) {
+  //       this.onError(e);
+  //     }
+
+  //     throw e;
+  //   }
+  // }
+
+  // constructor(remoteAddress, remotePort, options = {}) {
+  //   this.socket = null;
+  //   this.remoteAddress = remoteAddress;
+  //   this.remotePort = remotePort;
+  //   this.options = options;
+
+  //   // internals
+  //   this._readable = null;
+  //   this._writable = null;
+  //   this._reader = null;
+  //   this._writer;
+
+  //   // open and closed promise as fields
+  //   this._isOpenedSettled = false;
+  //   this._isClosedSettled = false;
+  //   this._openedPromise = new Promise((resolve, reject) => {
+  //     this._resolveOpened = (value) => {
+  //       this._isOpenedSettled = true;
+  //       resolve(value);
+  //     };
+
+  //     this._rejectOpened = (reason) => {
+  //       this._isOpenedSettled = true;
+  //       reject(reason);
+  //     };
+  //   });
+  //   this._closedPromise = new Promise((resolve, reject) => {
+  //     this._resolveClosed = (value) => {
+  //       this._isClosedSettled = true;
+  //       resolve(value);
+  //     };
+  //     this._rejectClosed = (reason) => {
+  //       this._isClosedSettled = true;
+  //       reject(reason);
+  //     };
+  //   });
+
+  //   // event callbacks
+  //   this.onOpen = null;
+  //   this.onData = null;
+  //   this.onClose = null;
+  //   this.onError = null;
+  // }
+
+  // get opened() {
+  //   return this._openedPromise;
+  // }
+
+  // get closed() {
+  //   return this._closedPromise;
+  // }
+
+  // async connect(timeoutMs = 5000) {
+  //   try {
+  //     let timeoutID;
+  //     // added timeout cause it seems to be standard
+  //     const timeoutPromise = new Promise((_, reject) => {
+  //       timeoutID = setTimeout(() => {
+  //         console.log(`Connection to ${this.remoteAddress}:${this.remotePort} timed out after ${timeoutMs}ms`);
+  //         reject(new Error("Connection timeout"));
+  //       }, timeoutMs);
+  //     });
+
+  //     this.socket = new TCPSocket(this.remoteAddress, this.remotePort, this.options);
+  //     // race between socket.opened and timeout
+  //     const openInfo = await Promise.race([this.socket.opened, timeoutPromise]);
+  //     clearTimeout(timeoutID);
+
+  //     this._readable = openInfo.readable;
+  //     this._writable = openInfo.writable;
+
+  //     if (this.onData) {
+  //       this._startReading();
+  //     }
+  //     if (this.onOpen) {
+  //       this.onOpen(openInfo);
+  //     }
+
+  //     this._resolveOpened(openInfo);
+  //     return openInfo;
+  //   } catch (e) {
+  //     this._rejectOpened(e);
+  //     this._rejectClosed(e);
+
+  //     if (this.onError) {
+  //       this.onError(e);
+  //     }
+
+  //     throw e;
+  //   }
+  // }
+
+  // async _startReading() {
+  //   try {
+  //     this._reader = this._readable.getReader();
+  //     while (true) {
+  //       const {value, done} = await this._reader.read();
+
+  //       if (done) {
+  //         // releaseLock() here
+  //         this._reader.releaseLock();
+  //         this._reader = null;
+  //         if (this.onClose) {
+  //           this.onClose();
+  //         }
+  //         break;
+  //       }
+  //       if (value && value.byteLength > 0) {
+  //         if (this.onData) {
+  //           this.onData(value);
+  //         }
+  //       }
+  //     }
+  //   } catch (e) {
+  //     if (this._reader) {
+  //       this._reader.releaseLock();
+  //       this._reader = null;
+  //     }
+  //     if (this.onClose) {
+  //       this.onClose();
+  //     }
+  //   }
+  // }
+
+  // async send(data) {
+  //   if (!this._writable) {
+  //     throw new Error(`Socket is not connected`);
+  //   }
+
+  //   try {
+  //     this._writer = this._writable.getWriter();
+  //     let buffer = data;
+  //     // old: for text exchange test, can probably be removed
+  //     if (typeof data === `string`) {
+  //       const encoder = new TextEncoder();
+  //       buffer = encoder.encode(data);
+  //     }
+
+  //     await this._writer.write(buffer);
+
+  //     await this._writer.releaseLock();
+  //     this._writer = null;
+  //     return true;
+  //   } catch (e) {
+  //     if (this.onError) {
+  //       this.onError(e);
+  //     }
+  //     throw e;
+  //   }
+  // }
+
+  // async close() {
+  //   if (!this.socket) {
+  //     throw new Error(`Socket is not connected`);
+  //   }
+
+
+  //   try {
+  //     // try to handle leftover locks if necessary, tho should have been handled in startReading's loop and send()
+  //     if (this._reader) {
+  //       this._reader.releaseLock();
+  //       this._reader = null;
+  //     }
+  //     if (this._writer) {
+  //       this._writer.releaseLock();
+  //       this._writer = null;
+  //     }
+
+  //     // returning this before trying to handle leftover locks errs because close before releaseLock(). I thought I had made it so it'd take care of that but guess not
+  //     // just try to release before fixes it
+  //     if (this._isClosedSettled) {
+  //       return this._closedPromise;
+  //     }
+
+
+  //     await this.socket.closed;
+  //     if (this.onClose) {
+  //       this.onClose();
+  //     }
+
+  //     this._resolveClosed();
+  //     return this._closedPromise;
+  //   } catch (e) {
+  //     this._rejectClosed(e);
+  //     if (this.onError) {
+  //       this.onError(e);
+  //     }
+      
+  //     throw e;
+  //   }
+  // }
+}
+
+
+export async function autoConfSockets() {
+  console.log(`AutoConfSockets running`);
+  return {tcpSocket: TCPSocketClient}
+}
+
+// export async function autoConfSockets({host, port, options}) {
+//   // console.log(`Creating new socket with: `, host, port, options);
+//   console.log("called autoConfSockets");
+//   // return new TCPSocketClient()
+// }
+
+// export async function connect() {
+  // return await sock.connect()
+// }

+ 1 - 0
nginx.conf

@@ -48,6 +48,7 @@ http {
             add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
             add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always;
         }
+
         add_header Content-Security-Policy "
             script-src 'self', 'wasm-unsafe-eval';
             script-src-elem 'self';

+ 1 - 1
package.json

@@ -5,7 +5,7 @@
 	"scripts": {
 		"dev": "vite dev",
 		"build": "CX_URL=assets/cx.esm.js vite build",
-		"postbuild": "node ./post_process.cjs && cp -r .well-known/ build/",
+		"postbuild": "node ./post_process.cjs && cp -r .well-known/ build/ && cp dSockets.js build/",
 		"iwa": "nginx -p . -c nginx.conf"
 	},
 	"devDependencies": {

+ 15 - 4
src/lib/TCPProxy.js

@@ -19,6 +19,7 @@ export class TCPProxy {
     this.onOutboundData = null;
     this.onInboundClose = null;
     this.onOutboundClose = null;
+    this.onClose = null;
     this.onError = null;
   }
 
@@ -53,6 +54,8 @@ export class TCPProxy {
             const {value: incomingSocket, done} = await this._reader.read();
             if (done) {
               console.log(`Server stopped accepting connections`);
+              this._reader.releaseLock();
+              this._reader = null;
               break;
             }
             this._handleIncomingConnection(incomingSocket); // can get client ID here if we have use for that later
@@ -62,8 +65,6 @@ export class TCPProxy {
           if (this.onError) {
             this.onError(e);
           }
-        } finally {
-          this._reader.releaseLock();
         }
       })();
       return openInfo;
@@ -291,14 +292,18 @@ export class TCPProxy {
     console.log(`Closing TCP proxy`);
     for (const [id, client] of this.inboundConnections.entries()) {
       try {
-        await client.close();
+        if (client.socket) {
+          await client.close();
+        }
       } catch (e) {
         console.error(`Error closing inbound connection ${id}: `, e);
       }
     }
     for (const [id, client] of this.outboundConnections.entries()) {
       try {
-        await client.close();
+        if (client.socket) {
+          await client.close();
+        }
       } catch (e) {
         console.error(`Error closing outbound connection ${id}: `, e);
       }
@@ -312,6 +317,8 @@ export class TCPProxy {
 
     if (this.server) {
       try {
+        // fixed Server close error by cancelling locked streams before close (sends done signal to loop)
+        await this._reader.cancel();
         await this.server.close();
       } catch (e) {
         console.error(`Error closing server: `, e);
@@ -319,6 +326,10 @@ export class TCPProxy {
       this.server = null;
     }
 
+    if (this.onClose) {
+      this.onClose();
+    }
+
     console.log("TCP Proxy closed");
   }
 

+ 9 - 5
src/lib/TCPSocketClient.js

@@ -155,11 +155,8 @@ export class TCPSocketClient {
       throw new Error(`Socket is not connected`);
     }
 
-    try {
-      if (this._isClosedSettled) {
-        return this._closedPromise;
-      }
 
+    try {
       // try to handle leftover locks if necessary, tho should have been handled in startReading's loop and send()
       if (this._reader) {
         this._reader.releaseLock();
@@ -170,7 +167,14 @@ export class TCPSocketClient {
         this._writer = null;
       }
 
-      await this.socket.close();
+      // returning this before trying to handle leftover locks errs because close before releaseLock(). I thought I had made it so it'd take care of that but guess not
+      // just try to release before fixes it
+      if (this._isClosedSettled) {
+        return this._closedPromise;
+      }
+
+
+      await this.socket.closed;
       if (this.onClose) {
         this.onClose();
       }

+ 0 - 68
src/lib/WebVM.svelte

@@ -11,7 +11,6 @@
 	import { introMessage, errorMessage, unexpectedErrorMessage } from '$lib/messages.js'
 	import { displayConfig, handleToolImpl } from '$lib/anthropic.js'
 	import { tryPlausible } from '$lib/plausible.js'
-	import { TCPProxy } from '$lib/TCPProxy.js'
 	import { TCPSocketClient } from '$lib/TCPSocketClient.js'
 
 	export let configObj = null;
@@ -192,75 +191,8 @@
 		if(display)
 			setScreenSize(display);
 	}
-	async function initProxy() {
-		const proxy = new TCPProxy();
-		proxy.onInboundConnect = (id, openInfo) => {
-		// 
-		// ideally link to destination here. Could be hard set or dynamically based on parameters
-		// 
-			console.log(`New inbound connection ${id} from ${openInfo.remoteAddress}:${openInfo.remotePort}`);
-		};
-		proxy.onOutboundConnect = (id, openInfo) => {
-			console.log(`New Outbound connection ${id} to ${openInfo.remoteAddress}:${openInfo.remotePort}`);
-		};
-		proxy.onInboundData = (id, data) => {
-			console.log(`[INBOUND] received from client ${id}: `, data);
-		};
-		proxy.onOutboundData = (id, data) => {
-			console.log(`[OUTBOUND] Received data from server ${id}: `, data);
-		};
-		proxy.onInboundClose = (id) => {
-			console.log(`Closed inbound connection ${id}`);
-		};
-		proxy.onOutboundClose = (id) => {
-			console.log(`Closed outbound connection ${id}`);
-		};
-		proxy.onError = (type, id, e) => {
-			console.error(`Error in ${type} ${id}: `, e);
-		};
-		await proxy.start('0.0.0.0', {localPort: 33000});
-		return proxy;
-	}
-	async function createMockInbound(proxy) {
-		const { readable, writable } = new TransformStream();
-		const mockSocket = {
-			opened: Promise.resolve({
-				readable,
-				writable,
-				remoteAddress: '127.0.0.1',
-				remotePort: 12345,
-				localAddress: '127.0.0.1',
-				localPort: 33000
-			})
-		};
-
-		// need to manually trigger event cause its fake so startReading's loop won't catch this as real connection
-		return proxy.acceptConnection(mockSocket);
-	}
 	async function initTerminal()
 	{
-		const proxy = await initProxy();
-		console.log(`We have proxy`);
-
-		const mockInboundID = await createMockInbound(proxy);
-		console.log(`Created mock inbound connection ${mockInboundID}`);
-
-		const link = await proxy.link(mockInboundID, `google.com`, 80);
-		console.log(`Linked mock client ${mockInboundID} to google ${link.outboundID}`);
-
-		const inboundClient = proxy.inboundConnections.get(mockInboundID);
-		if (inboundClient) {
-		 console.log(`We have a client`);
-		 const someData =
-		 				"GET / HTTP/1.1\r\n" +
-		 				"Host: google.com\r\n" +
-		 				"Connection: close\r\n\r\n";
-		 await inboundClient.send(someData);
-		 console.log(`Sent data to ${link.outboundID}`);
-		}
-		// await proxy.close();
-
-
 		const { Terminal } = await import('@xterm/xterm');
 		const { FitAddon } = await import('@xterm/addon-fit');
 		const { WebLinksAddon } = await import('@xterm/addon-web-links');

+ 2 - 3
src/lib/network.js

@@ -1,5 +1,6 @@
 import { writable } from 'svelte/store';
 import { browser } from '$app/environment';
+import { TCPSocketClient } from '../../dSockets';
 
 let authKey = undefined;
 let controlUrl = undefined;
@@ -156,7 +157,5 @@ export const networkData = { currentIp: null, connectionState: connectionState,
 //
 // IWA test
 // 
-let host = "127.0.0.1"; // localhost
-let port = 4321;
-export const directSocketsInterface = { host: host, port: port };
+export const directSocketsInterface = { host: null, port: null, options: null};
 

+ 10 - 0
src/lib/streams.js

@@ -17,6 +17,7 @@
  //
  // -- MODIFIED TO JS FOR WEBVM --
  // 
+ //
 
 export async function readStream(
   reader,
@@ -79,3 +80,12 @@ export async function collectConnections(
   await server.closed;
   console.log('Closed');
 }
+
+
+// export async function autoConfSockets({host, port, options}) {
+//   client = new TCPSocketClient(host, port, options);
+//   client.connect();
+//   client._startReading();
+
+//   return client;
+// }

+ 0 - 2
svelte.config.js

@@ -10,7 +10,6 @@ const config = {
 				'script-src': ['self', 'wasm-unsafe-eval'],
 				'script-src-elem': ['self', 'wasm-unsafe-eval'],
 				'connect-src': ['self', 'https:', 'blob:', 'data:', 'wss:'],
-				'require-trusted-types-for': ['script'],
 				'frame-src': ['self', 'https:', 'blob:', 'data:'],
 				'img-src': ['self', 'https:', 'blob:', 'data:'],
 				'media-src': ['self', 'https:', 'blob:', 'data:'],
@@ -25,7 +24,6 @@ const config = {
 				'script-src': ['self', 'wasm-unsafe-eval'],
 				'connect-src': ['self', 'https:', 'blob:', 'data:', 'wss:'],
 				'worker-src': ['self', 'wasm-unsafe-eval', 'blob:'],
-				'require-trusted-types-for': ['script'],
 				'frame-src': ['self', 'https:', 'blob:', 'data:'],
 				'img-src': ['self', 'https:', 'blob:', 'data:'],
 				'media-src': ['self', 'https:', 'blob:', 'data:'],

File diff suppressed because it is too large
+ 0 - 0
tun/ipstack.js


BIN
tun/ipstack.wasm


+ 7 - 2
tun/tailscale_tun.js

@@ -12,6 +12,9 @@ export const State = {
 	Running: 6,
 };
 
+const {IpStacke}= await ipStackAwait()
+IpStacke.init();
+
 export async function init() {
 	const {IpStack} = await ipStackAwait();
 	IpStack.init();
@@ -81,8 +84,8 @@ export async function init() {
 
 
 	return {
-		tcpSocket: IpStack.TCPSocket.create,
-		udpSocket: IpStack.UDPSocket.create,
+		tcpSocket: IpStack.TCPSocket,
+		udpSocket: IpStack.UDPSocket,
 		parseIP: IpStack.parseIP,
 		dumpIP: IpStack.dumpIP,
 		resolve: IpStack.resolve,
@@ -103,5 +106,7 @@ export async function init() {
 		logout: () => ipn.logout(),
 		listeners
 	};
+
 }
 
+export const dumpIP = IpStacke.dumpIP;

Some files were not shown because too many files changed in this diff