diff --git a/package.json b/package.json index 02f490d..9548b7a 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,10 @@ "license": "ISC", "devDependencies": { "@cloudflare/workers-types": "^4.20230518.0", - "wrangler": "^3.0.1" + "wrangler": "^3.1.0" }, "dependencies": { + "dns-packet": "^5.6.0", "ip-cidr": "^3.1.0", "ip-range-check": "^0.2.0", "ipaddr.js": "^2.0.1" diff --git a/src/worker-vless.js b/src/worker-vless.js index 2a7a645..f9ec8dc 100644 --- a/src/worker-vless.js +++ b/src/worker-vless.js @@ -81,13 +81,14 @@ async function vlessOverWSHandler(request) { let remoteSocketWapper = { value: null, }; + let udpStreamWrite = null; let isDns = false; // ws --> remote readableWebSocketStream.pipeTo(new WritableStream({ async write(chunk, controller) { if (isDns) { - return await handleDNSQuery(chunk, webSocket, null, log); + return udpStreamWrite(chunk); } if (remoteSocketWapper.value) { const writer = remoteSocketWapper.value.writable.getWriter() @@ -128,8 +129,12 @@ async function vlessOverWSHandler(request) { const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); const rawClientData = chunk.slice(rawDataIndex); + // TODO: support udp here when cf runtime has udp support if (isDns) { - return handleDNSQuery(rawClientData, webSocket, vlessResponseHeader, log); + const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); + udpStreamWrite = write; + udpStreamWrite(rawClientData); + return; } handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); }, @@ -515,54 +520,77 @@ function stringify(arr, offset = 0) { return uuid; } + /** * - * @param {ArrayBuffer} udpChunk * @param {import("@cloudflare/workers-types").WebSocket} webSocket * @param {ArrayBuffer} vlessResponseHeader * @param {(string)=> void} log */ -async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) { - // no matter which DNS server client send, we alwasy use hard code one. - // beacsue someof DNS server is not support DNS over TCP - try { - const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug - const dnsPort = 53; - /** @type {ArrayBuffer | null} */ - let vlessHeader = vlessResponseHeader; - /** @type {import("@cloudflare/workers-types").Socket} */ - const tcpSocket = connect({ - hostname: dnsServer, - port: dnsPort, - }); +async function handleUDPOutBound(webSocket, vlessResponseHeader, log) { - log(`connected to ${dnsServer}:${dnsPort}`); - const writer = tcpSocket.writable.getWriter(); - await writer.write(udpChunk); - writer.releaseLock(); - await tcpSocket.readable.pipeTo(new WritableStream({ - async write(chunk) { - if (webSocket.readyState === WS_READY_STATE_OPEN) { - if (vlessHeader) { - webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); - vlessHeader = null; - } else { - webSocket.send(chunk); - } + let isVlessHeaderSent = false; + const transformStream = new TransformStream({ + start(controller) { + + }, + transform(chunk, controller) { + // udp message 2 byte is the the length of udp data + // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message + for (let index = 0; index < chunk.byteLength;) { + const lengthBuffer = chunk.slice(index, index + 2); + const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); + const udpData = new Uint8Array( + chunk.slice(index + 2, index + 2 + udpPakcetLength) + ); + index = index + 2 + udpPakcetLength; + controller.enqueue(udpData); + } + }, + flush(controller) { + } + }); + + // only handle dns udp for now + transformStream.readable.pipeTo(new WritableStream({ + async write(chunk) { + const resp = await fetch('https://1.1.1.1/dns-query', + { + method: 'POST', + headers: { + 'content-type': 'application/dns-message', + }, + body: chunk, + }) + const dnsQueryResult = await resp.arrayBuffer(); + const udpSize = dnsQueryResult.byteLength; + // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16))); + const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]); + if (webSocket.readyState === WS_READY_STATE_OPEN) { + log(`doh success and dns message length is ${udpSize}`); + if (isVlessHeaderSent) { + webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); + } else { + webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); + isVlessHeaderSent = true; } - }, - close() { - log(`dns server(${dnsServer}) tcp is close`); - }, - abort(reason) { - console.error(`dns server(${dnsServer}) tcp is abort`, reason); - }, - })); - } catch (error) { - console.error( - `handleDNSQuery have exception, error: ${error.message}` - ); - } + } + } + })).catch((error) => { + log('dns udp has error' + error) + }); + + const writer = transformStream.writable.getWriter(); + + return { + /** + * + * @param {Uint8Array} chunk + */ + write(chunk) { + writer.write(chunk); + } + }; } /** diff --git a/wrangler.toml b/wrangler.toml index 7ad3e18..3669fc9 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -8,4 +8,4 @@ main = "src/worker-vless.js" compatibility_date = "2023-05-26" [vars] -UUID = "example_dev_token" \ No newline at end of file +# UUID = "example_dev_token" \ No newline at end of file