add doh for udp dns

This commit is contained in:
Emo-Damage 2023-06-22 23:09:14 +08:00
parent a6f0064a4c
commit 2b9927a1b1
3 changed files with 73 additions and 44 deletions

View File

@ -11,9 +11,10 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20230518.0", "@cloudflare/workers-types": "^4.20230518.0",
"wrangler": "^3.0.1" "wrangler": "^3.1.0"
}, },
"dependencies": { "dependencies": {
"dns-packet": "^5.6.0",
"ip-cidr": "^3.1.0", "ip-cidr": "^3.1.0",
"ip-range-check": "^0.2.0", "ip-range-check": "^0.2.0",
"ipaddr.js": "^2.0.1" "ipaddr.js": "^2.0.1"

View File

@ -81,13 +81,14 @@ async function vlessOverWSHandler(request) {
let remoteSocketWapper = { let remoteSocketWapper = {
value: null, value: null,
}; };
let udpStreamWrite = null;
let isDns = false; let isDns = false;
// ws --> remote // ws --> remote
readableWebSocketStream.pipeTo(new WritableStream({ readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) { async write(chunk, controller) {
if (isDns) { if (isDns) {
return await handleDNSQuery(chunk, webSocket, null, log); return udpStreamWrite(chunk);
} }
if (remoteSocketWapper.value) { if (remoteSocketWapper.value) {
const writer = remoteSocketWapper.value.writable.getWriter() const writer = remoteSocketWapper.value.writable.getWriter()
@ -128,8 +129,12 @@ async function vlessOverWSHandler(request) {
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
const rawClientData = chunk.slice(rawDataIndex); const rawClientData = chunk.slice(rawDataIndex);
// TODO: support udp here when cf runtime has udp support
if (isDns) { 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); handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
}, },
@ -515,54 +520,77 @@ function stringify(arr, offset = 0) {
return uuid; return uuid;
} }
/** /**
* *
* @param {ArrayBuffer} udpChunk
* @param {import("@cloudflare/workers-types").WebSocket} webSocket * @param {import("@cloudflare/workers-types").WebSocket} webSocket
* @param {ArrayBuffer} vlessResponseHeader * @param {ArrayBuffer} vlessResponseHeader
* @param {(string)=> void} log * @param {(string)=> void} log
*/ */
async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) { async function handleUDPOutBound(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,
});
log(`connected to ${dnsServer}:${dnsPort}`); let isVlessHeaderSent = false;
const writer = tcpSocket.writable.getWriter(); const transformStream = new TransformStream({
await writer.write(udpChunk); start(controller) {
writer.releaseLock();
await tcpSocket.readable.pipeTo(new WritableStream({ },
async write(chunk) { transform(chunk, controller) {
if (webSocket.readyState === WS_READY_STATE_OPEN) { // udp message 2 byte is the the length of udp data
if (vlessHeader) { // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); for (let index = 0; index < chunk.byteLength;) {
vlessHeader = null; const lengthBuffer = chunk.slice(index, index + 2);
} else { const udpPakcetLength = new DataView(lengthBuffer).getUint16(0);
webSocket.send(chunk); 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`); })).catch((error) => {
}, log('dns udp has error' + error)
abort(reason) { });
console.error(`dns server(${dnsServer}) tcp is abort`, reason);
}, const writer = transformStream.writable.getWriter();
}));
} catch (error) { return {
console.error( /**
`handleDNSQuery have exception, error: ${error.message}` *
); * @param {Uint8Array} chunk
} */
write(chunk) {
writer.write(chunk);
}
};
} }
/** /**

View File

@ -8,4 +8,4 @@ main = "src/worker-vless.js"
compatibility_date = "2023-05-26" compatibility_date = "2023-05-26"
[vars] [vars]
UUID = "example_dev_token" # UUID = "example_dev_token"