Created
January 16, 2024 09:14
-
-
Save devsnek/d8f0be4aa7b2d2eeb28325ca29c605ee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { decodeBase64 } from "https://deno.land/[email protected]/encoding/base64.ts"; | |
const CLIENT_ID = crypto.randomUUID(); | |
const [{ port, target }] = await Deno.resolveDns( | |
"_v2-origintunneld._tcp.argotunnel.com", | |
"SRV", | |
); | |
const [address] = await Deno.resolveDns(target, "A"); | |
const quickTun = await fetch("https://api.trycloudflare.com/tunnel", { | |
method: "POST", | |
}).then((r) => r.json()); | |
console.log(quickTun); | |
const conn = await Deno.connectQuic({ | |
hostname: address, | |
port, | |
serverName: "quic.cftunnel.com", | |
caCerts: [ | |
await Deno.readTextFile("./cf_root.pem"), | |
], | |
}); | |
const helperSock = await Deno.connect({ port: 8080 }); | |
const encoder = new TextEncoder(); | |
const decoder = new TextDecoder('utf8'); | |
const writer = helperSock.writable.getWriter(); | |
const reader = helperSock.readable.getReader(); | |
const rpc = async (req: object) => { | |
await writer.write(encoder.encode(JSON.stringify(req) + '\n')); | |
const { value } = await reader.read(); | |
return JSON.parse(decoder.decode(value)); | |
}; | |
handleControlStream(conn); | |
handleRequests(conn); | |
async function handleControlStream(quic: Deno.QuicConn) { | |
const bi = await quic.createBidirectionalStream(); | |
const pain1 = await Deno.connect({ port: 8080 }); | |
pain1.readable.pipeTo(bi.writable).catch(console.error); | |
bi.readable.pipeTo(pain1.writable).catch(console.error); | |
console.log(await rpc({ | |
RegisterConnection: { | |
account_tag: quickTun.result.account_tag, | |
tunnel_secret: [...decodeBase64(quickTun.result.secret)], | |
tunnel_id: quickTun.result.id, | |
conn_index: 0, | |
client_id: CLIENT_ID, | |
}, | |
})); | |
} | |
const DATA_SIG = [0x0A, 0x36, 0xCD, 0x12, 0xA1, 0x3E]; | |
function isSig(got: number[], cmp: number[]) { | |
if (got.length !== cmp.length) { | |
return false; | |
} | |
for (let i = 0; i < got.length; i += 1) { | |
if (got[i] !== cmp[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
async function handleRequests(conn: Deno.QuicConn) { | |
for await (const bi of conn.incomingBidirectionalStreams) { | |
const reader = bi.readable.getReader({ mode: 'byob' }); | |
const writer = bi.writable.getWriter(); | |
const { value: sig } = await reader.read(new Uint8Array(6)); | |
if (isSig(sig, DATA_SIG)) { | |
const { value: version } = await reader.read(new Uint8Array(2)); | |
const req = await readQuicRequest(reader); | |
let body = encoder.encode('no body :('); | |
const bodyLen = req.metadata['HttpHeader:Content-Length']; | |
if (bodyLen) { | |
({ value: body } = await reader.read(new Uint8Array(+bodyLen))); | |
} | |
await writer.write(new Uint8Array(DATA_SIG)); | |
await writer.write(new Uint8Array([0x30, 0x31])); | |
const res = await rpc({ | |
QuicConnectResponse: { | |
metadata: { | |
'HttpStatus': '200', | |
'HttpHeader:Content-Length': body.length.toString(), | |
'HttpHeader:Content-Type': 'text/plain', | |
}, | |
}, | |
}); | |
await writer.write(new Uint8Array(res.data)); | |
await writer.write(body); | |
await writer.close(); | |
} else { | |
console.log('unknown sig', sig); | |
} | |
} | |
} | |
async function readQuicRequest(reader: ReadableStreamBYOBReader) { | |
const data = []; | |
const { value: buf } = await reader.read(new Uint8Array(8)); | |
data.push(...buf!); | |
const [segmentCount, firstSegmentLen] = parseSegmentTableFirst(buf!); | |
let totalSegmentLength = firstSegmentLen; | |
if (segmentCount > 1) { | |
const { value: segmentSizes } = await reader.read(new Uint8Array(segmentCount * 4)); | |
data.push(segmentSizes); | |
const view = new DataView(segmentSizes!.buffer); | |
for (let i = 0; i < (segmentCount - 1); i += 1) { | |
const len = view.getUint32(i * 4, true); | |
totalSegmentLength += len; | |
} | |
} | |
const { value: encoded } = await reader.read(new Uint8Array(totalSegmentLength * 8)); | |
const req = await rpc({ | |
QuicConnectRequest: { | |
data: [...data, ...encoded!], | |
}, | |
}); | |
return req; | |
} | |
function parseSegmentTableFirst(buf: Uint8Array) { | |
const view = new DataView(buf.buffer); | |
const segmentCount = view.getUint32(0, true) + 1; | |
if (segmentCount > 512 || segmentCount === 0) { | |
throw new Error(`Invalid segment count: ${segmentCount}`); | |
} | |
const firstSegmentLen = view.getUint32(4, true); | |
return [segmentCount, firstSegmentLen]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment