Skip to content

Instantly share code, notes, and snippets.

@devsnek
Created January 16, 2024 09:14
Show Gist options
  • Save devsnek/d8f0be4aa7b2d2eeb28325ca29c605ee to your computer and use it in GitHub Desktop.
Save devsnek/d8f0be4aa7b2d2eeb28325ca29c605ee to your computer and use it in GitHub Desktop.
import { decodeBase64 } from "https://deno.land/std@0.212.0/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