Skip to content

Commit 2b9927a

Browse files
committed
add doh for udp dns
1 parent a6f0064 commit 2b9927a

File tree

3 files changed

+73
-44
lines changed

3 files changed

+73
-44
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
"license": "ISC",
1212
"devDependencies": {
1313
"@cloudflare/workers-types": "^4.20230518.0",
14-
"wrangler": "^3.0.1"
14+
"wrangler": "^3.1.0"
1515
},
1616
"dependencies": {
17+
"dns-packet": "^5.6.0",
1718
"ip-cidr": "^3.1.0",
1819
"ip-range-check": "^0.2.0",
1920
"ipaddr.js": "^2.0.1"

src/worker-vless.js

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,14 @@ async function vlessOverWSHandler(request) {
8181
let remoteSocketWapper = {
8282
value: null,
8383
};
84+
let udpStreamWrite = null;
8485
let isDns = false;
8586

8687
// ws --> remote
8788
readableWebSocketStream.pipeTo(new WritableStream({
8889
async write(chunk, controller) {
8990
if (isDns) {
90-
return await handleDNSQuery(chunk, webSocket, null, log);
91+
return udpStreamWrite(chunk);
9192
}
9293
if (remoteSocketWapper.value) {
9394
const writer = remoteSocketWapper.value.writable.getWriter()
@@ -128,8 +129,12 @@ async function vlessOverWSHandler(request) {
128129
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
129130
const rawClientData = chunk.slice(rawDataIndex);
130131

132+
// TODO: support udp here when cf runtime has udp support
131133
if (isDns) {
132-
return handleDNSQuery(rawClientData, webSocket, vlessResponseHeader, log);
134+
const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
135+
udpStreamWrite = write;
136+
udpStreamWrite(rawClientData);
137+
return;
133138
}
134139
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
135140
},
@@ -515,54 +520,77 @@ function stringify(arr, offset = 0) {
515520
return uuid;
516521
}
517522

523+
518524
/**
519525
*
520-
* @param {ArrayBuffer} udpChunk
521526
* @param {import("@cloudflare/workers-types").WebSocket} webSocket
522527
* @param {ArrayBuffer} vlessResponseHeader
523528
* @param {(string)=> void} log
524529
*/
525-
async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) {
526-
// no matter which DNS server client send, we alwasy use hard code one.
527-
// beacsue someof DNS server is not support DNS over TCP
528-
try {
529-
const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug
530-
const dnsPort = 53;
531-
/** @type {ArrayBuffer | null} */
532-
let vlessHeader = vlessResponseHeader;
533-
/** @type {import("@cloudflare/workers-types").Socket} */
534-
const tcpSocket = connect({
535-
hostname: dnsServer,
536-
port: dnsPort,
537-
});
530+
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
538531

539-
log(`connected to ${dnsServer}:${dnsPort}`);
540-
const writer = tcpSocket.writable.getWriter();
541-
await writer.write(udpChunk);
542-
writer.releaseLock();
543-
await tcpSocket.readable.pipeTo(new WritableStream({
544-
async write(chunk) {
545-
if (webSocket.readyState === WS_READY_STATE_OPEN) {
546-
if (vlessHeader) {
547-
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
548-
vlessHeader = null;
549-
} else {
550-
webSocket.send(chunk);
551-
}
532+
let isVlessHeaderSent = false;
533+
const transformStream = new TransformStream({
534+
start(controller) {
535+
536+
},
537+
transform(chunk, controller) {
538+
// udp message 2 byte is the the length of udp data
539+
// TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
540+
for (let index = 0; index < chunk.byteLength;) {
541+
const lengthBuffer = chunk.slice(index, index + 2);
542+
const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
543+
const udpData = new Uint8Array(
544+
chunk.slice(index + 2, index + 2 + udpPakcetLength)
545+
);
546+
index = index + 2 + udpPakcetLength;
547+
controller.enqueue(udpData);
548+
}
549+
},
550+
flush(controller) {
551+
}
552+
});
553+
554+
// only handle dns udp for now
555+
transformStream.readable.pipeTo(new WritableStream({
556+
async write(chunk) {
557+
const resp = await fetch('https://1.1.1.1/dns-query',
558+
{
559+
method: 'POST',
560+
headers: {
561+
'content-type': 'application/dns-message',
562+
},
563+
body: chunk,
564+
})
565+
const dnsQueryResult = await resp.arrayBuffer();
566+
const udpSize = dnsQueryResult.byteLength;
567+
// console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16)));
568+
const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]);
569+
if (webSocket.readyState === WS_READY_STATE_OPEN) {
570+
log(`doh success and dns message length is ${udpSize}`);
571+
if (isVlessHeaderSent) {
572+
webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer());
573+
} else {
574+
webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
575+
isVlessHeaderSent = true;
552576
}
553-
},
554-
close() {
555-
log(`dns server(${dnsServer}) tcp is close`);
556-
},
557-
abort(reason) {
558-
console.error(`dns server(${dnsServer}) tcp is abort`, reason);
559-
},
560-
}));
561-
} catch (error) {
562-
console.error(
563-
`handleDNSQuery have exception, error: ${error.message}`
564-
);
565-
}
577+
}
578+
}
579+
})).catch((error) => {
580+
log('dns udp has error' + error)
581+
});
582+
583+
const writer = transformStream.writable.getWriter();
584+
585+
return {
586+
/**
587+
*
588+
* @param {Uint8Array} chunk
589+
*/
590+
write(chunk) {
591+
writer.write(chunk);
592+
}
593+
};
566594
}
567595

568596
/**

wrangler.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ main = "src/worker-vless.js"
88
compatibility_date = "2023-05-26"
99

1010
[vars]
11-
UUID = "example_dev_token"
11+
# UUID = "example_dev_token"

0 commit comments

Comments
 (0)