Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/commons/envutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ export function tlsKey() {
return envManager.get("TLS_KEY") || null;
}

export function allowDomainFronting() {
if (!envManager) return false;
return envManager.get("TLS_ALLOW_ANY_SNI") || false;
}

export function kdfSvcSecretHex() {
if (!envManager) return null;
return envManager.get("KDF_SVC") || null;
Expand Down Expand Up @@ -407,7 +412,7 @@ export function logpushSecretKey() {
if (!envManager) return "";

const secretkey = envManager.get("CF_LOGPUSH_R2_SECRET_KEY") || "";
if (onCloudflare() || onLocal()) return secretkey;
if (onCloudflare() || onLocal()) return secretkey || "";

return "";
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/doh.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function handleRequest(event) {
*/
async function proxyRequest(event) {
if (optionsRequest(event.request)) return util.respond204();
if (headRequest(event.request)) return util.respond204();

const io = new IOState();
const ua = event.request.headers.get("User-Agent");
Expand Down Expand Up @@ -61,6 +62,10 @@ function optionsRequest(request) {
return request.method === "OPTIONS";
}

function headRequest(request) {
return request.method === "HEAD";
}

/**
* Must not throw!
* @param {IOState} io
Expand Down
6 changes: 6 additions & 0 deletions src/core/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ const defaults = new Map(
type: "boolean",
default: false,
},
// if true, do not validate the SNI field in TLS handshake
// effectively allowing clients to "fake" SNI
TLS_ALLOW_ANY_SNI: {
type: "boolean",
default: false,
},
// global log level (debug, info, warn, error)
LOG_LEVEL: {
type: "string",
Expand Down
1 change: 1 addition & 0 deletions src/core/node/blocklists.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ async function fmmap(fp) {
const isDeno = envutil.isDeno();

if (dynimports && isNode) {
log.i("mmap f:", fp, "on node");
try {
const mmap = (await import("@riaskov/mmap-io")).default;
const fd = fs.openSync(fp, "r+");
Expand Down
15 changes: 12 additions & 3 deletions src/plugins/cache-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ export function isAnswerFresh(m, n = 0) {
export function updatedAnswer(dnsPacket, qid, expiry) {
updateQueryId(dnsPacket, qid);
updateTtl(dnsPacket, expiry);
retainOneAnswer(dnsPacket);
trimAQuadAAnswer(dnsPacket);
return dnsPacket;
}

function retainOneAnswer(decodedDnsPacket) {
function trimAQuadAAnswer(decodedDnsPacket) {
// retain only the first answer, drop the rest
if (
!dnsutil.hasSingleQuestion(decodedDnsPacket) ||
Expand All @@ -270,11 +270,20 @@ function retainOneAnswer(decodedDnsPacket) {
return;
}

let dotrim = false;
const trimmed = new Array(0);
for (const a of decodedDnsPacket.answers) {
if (dnsutil.isAnswerCname(a)) {
trimmed.push(a);
}
if (dnsutil.isAnswerA(a) || dnsutil.isAnswerAAAA(a)) {
decodedDnsPacket.answers = [a];
trimmed.push(a);
dotrim = true;
break;
}
}
if (dotrim) {
decodedDnsPacket.answers = trimmed;
} // else: nothing to trim, return as-is
return;
}
2 changes: 1 addition & 1 deletion src/plugins/dns-op/cache-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class CacheApi {
/**
* @param {string} href
* @param {Response} response
* @returns
* @returns {Promise<any>}
*/
put(href, response) {
if (this.noop) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dns-op/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class DnsCache {
/**
* @param {URL} url
* @param {cacheutil.DnsCacheData} data
* @returns
* @returns {Promise<any|void>}
*/
async putHttpCache(url, data) {
const k = url.href;
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/dns-op/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ DNSResolver.prototype.resolveDnsUpstream = async function (
dnsreq = new Request(u.href, {
method: "GET",
headers: util.dnsHeaders(),
signal: AbortSignal.timeout(this.timeout),
});
} else if (util.isPostRequest(request)) {
dnsreq = new Request(u.href, {
Expand All @@ -458,10 +459,12 @@ DNSResolver.prototype.resolveDnsUpstream = async function (
util.dnsHeaders()
),
body: query,
signal: AbortSignal.timeout(this.timeout),
});
} else {
throw new Error("get/post only");
}

this.log.d(rxid, "upstream doh2/fetch", u.href);
promisedPromises.push(fetch(dnsreq));
}
Expand Down
98 changes: 48 additions & 50 deletions src/server-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class Tracker {
}

cmap.set(connid, new ConnW(sock));
sock.on("close", (haderr) => cmap.delete(connid));
sock.on("close", (_haderr) => cmap.delete(connid));

return connid;
}
Expand Down Expand Up @@ -238,7 +238,7 @@ const maxHeapSnaps = 20;
const maxCertUpdateAttempts = 20;
let adjTimer = null;

((main) => {
((_main) => {
// listen for "go" and start the server
system.sub("go", systemUp);
// listen for "end" and stop the server
Expand Down Expand Up @@ -452,7 +452,10 @@ function systemUp() {
* @param {int} n
*/
async function certUpdateForever(secopts, s, n = 0) {
if (n > maxCertUpdateAttempts) return false;
if (n > maxCertUpdateAttempts) {
console.error("crt: max update attempts reached", n);
return false;
}

const crtpem = secopts.cert;
if (bufutil.emptyBuf(crtpem)) {
Expand Down Expand Up @@ -567,7 +570,7 @@ function trapServerEvents(id, s) {
});

// emitted when the req is discarded due to maxConnections
s.on("drop", (data) => {
s.on("drop", (_data) => {
stats.nofdrops += 1;
stats.nofconns += 1;
});
Expand Down Expand Up @@ -665,34 +668,21 @@ function trapSecureServerEvents(id, s) {
});

// emitted when the req is discarded due to maxConnections
s.on("drop", (data) => {
s.on("drop", (_data) => {
stats.nofdrops += 1;
stats.nofconns += 1;
});

s.on("tlsClientError", (err, /** @type {TLSSocket} */ tlsSocket) => {
stats.tlserr += 1;
// fly tcp healthchecks also trigger tlsClientErrors
log.d("tls: client err;", err.message, addrstr(tlsSocket));
// log.d("tls: client err;", err.message, addrstr(tlsSocket));
close(tlsSocket);
});
}

/**
* @param {TLSSocket|Socket} sock
*/
function addrstr(sock) {
if (!sock) return "";
if (sock.localAddress == null || sock.remoteAddress == null) return "";
return (
`[${sock.localAddress}]:${sock.localPort}` +
"->" +
`[${sock.remoteAddress}]:${sock.remotePort}`
);
}

/**
* @param {tls.Server} s
* @param {tls.Server?} s
* @returns {void}
*/
function rotateTkt(s) {
Expand Down Expand Up @@ -725,7 +715,7 @@ function up(server, addr) {

/**
* RST and/or closes tcp socket.
* @param {Socket | TLSSocket} sock
* @param {Socket | TLSSocket | null} sock
*/
function close(sock) {
if (!sock || sock.destroyed) return;
Expand All @@ -735,14 +725,14 @@ function close(sock) {
}

/**
* @param {Http2ServerResponse} res
* @param {Http2ServerResponse?} res
*/
function resClose(res) {
if (res && !res.destroy) res.destroy();
}

/**
* @param {Http2ServerResponse} res
* @param {Http2ServerResponse?} res
* @returns {Boolean}
*/
function resOkay(res) {
Expand All @@ -751,20 +741,21 @@ function resOkay(res) {
}

/**
* @param {Socket} sock
* @param {Socket?} sock
* @returns {Boolean}
*/
function tcpOkay(sock) {
return sock.writable;
return sock && sock.writable;
}

/**
* Creates a duplex pipe between `a` and `b` sockets.
* @param {Socket} a
* @param {Socket} b
* @param {Socket?} a
* @param {Socket?} b
* @return {Boolean} - true if pipe created, false if error
*/
function proxySockets(a, b) {
if (!a || !b) return false;
if (a.destroyed || b.destroyed) return false;
// handle errors? stackoverflow.com/a/61091744
a.pipe(b);
Expand Down Expand Up @@ -825,7 +816,7 @@ function serveDoTProxyProto(clientSocket) {
}
}

clientSocket.on("error", (e) => {
clientSocket.on("error", (_e) => {
log.w("pp: client err, closing");
close(clientSocket);
close(dotSock);
Expand Down Expand Up @@ -911,10 +902,15 @@ function getDnRE(socket) {

/**
* Gets flag and hostname from the wildcard domain name.
* @param {String} sni - Wildcard SNI
* @return {Array<String>} [flag, hostname]
* @param {String?} sni - Wildcard SNI
* @return {<[String, String]>} [flag, hostname] - may be empty strings
*/
function getMetadata(sni) {
const fakesni = envutil.allowDomainFronting();

if (util.emptyString(sni)) {
return ["", fakesni ? "nosni.tld" : ""];
}
// 1-flag.max.rethinkdns.com => ["1-flag", "max", "rethinkdns", "com"]
// 1-flag.somedomain.tld => ["1-flag", "somedomain", "tld"]
const s = sni.split(".");
Expand All @@ -931,7 +927,7 @@ function getMetadata(sni) {
return [flag, host];
} else {
// sni => max.rethinkdns.com
log.d(`flag: "", host: ${host}`);
log.d(`flag: "", host: ${sni}`);
return ["", sni];
}
}
Expand All @@ -942,23 +938,25 @@ function getMetadata(sni) {
*/
function serveTLS(socket) {
const sni = socket.servername;
if (!sni) {
log.d("no sni, close conn");
close(socket);
return;
}
if (!envutil.allowDomainFronting()) {
if (!sni) {
log.d("no sni, close conn");
close(socket);
return;
}

if (!OUR_RG_DN_RE || !OUR_WC_DN_RE) {
[OUR_RG_DN_RE, OUR_WC_DN_RE] = getDnRE(socket);
}
if (!OUR_RG_DN_RE || !OUR_WC_DN_RE) {
[OUR_RG_DN_RE, OUR_WC_DN_RE] = getDnRE(socket);
}

const isOurRgDn = OUR_RG_DN_RE.test(sni);
const isOurWcDn = OUR_WC_DN_RE.test(sni);
const isOurRgDn = OUR_RG_DN_RE.test(sni);
const isOurWcDn = OUR_WC_DN_RE.test(sni);

if (!isOurWcDn && !isOurRgDn) {
log.w("unexpected sni, close conn", sni);
close(socket);
return;
if (!isOurWcDn && !isOurRgDn) {
log.w("unexpected sni, close conn", sni);
close(socket);
return;
}
}

if (false) {
Expand All @@ -969,10 +967,10 @@ function serveTLS(socket) {
log.d(`(${proto}), reused? ${reused}; ticket: ${tkt}; sess: ${sess}`);
}

const [flag, host] = isOurWcDn ? getMetadata(sni) : ["", sni];
const [flag, host] = getMetadata(sni);
const sb = new ScratchBuffer();

log.d("----> dot request", host, flag);
log.d("----> dot request", flag, host);
socket.on("data", async (data) => {
const len = await handleTCPData(socket, data, sb, host, flag);
adjustTLSFragAfterWrites(socket, len);
Expand All @@ -998,7 +996,7 @@ function serveTCP(socket) {
/**
* Handle DNS over TCP/TLS data stream.
* @param {Socket} socket
* @param {Buffer} chunk - A TCP data segment
* @param {ArrayBuffer} chunk - A TCP data segment
* @param {ScratchBuffer} sb - Scratch buffer
* @param {String} host - Hostname
* @param {String} flag - Blocklist Flag
Expand Down Expand Up @@ -1037,7 +1035,7 @@ async function handleTCPData(socket, chunk, sb, host, flag) {
// chunk out dns-query starting rem-th byte
const data = chunk.slice(rem, qlimit);
// out of band data, if any
const oob = chunk.slice(qlimit);
const oob = qlimit < cl ? chunk.slice(qlimit) : null;

sb.allocOnce(qlen);

Expand Down Expand Up @@ -1105,7 +1103,7 @@ async function handleTCPQuery(q, socket, host, flag) {
* @param {string} rxid
* @param {Socket} socket
* @param {Uint8Array} data
* @param {int} n - bytes written to socket
* @returns {int} n - bytes written to socket
*/
function measuredWrite(rxid, socket, data) {
let ok = tcpOkay(socket);
Expand Down
Loading