Files
safetynet/scripts/webtransport-native-proof.mjs
T

25 lines
4.7 KiB
JavaScript

import {spawn} from 'node:child_process';
import {createRequire} from 'node:module';
import {createServer,request} from 'node:http';
import {mkdtempSync,readFileSync,writeFileSync} from 'node:fs';
import {resolve,join} from 'node:path';
import assert from 'node:assert/strict';
const require=createRequire(import.meta.url),root=mkdtempSync(resolve('artifacts/webtransport-')),env={...process.env};delete env.ELECTRON_RUN_AS_NODE;
const python=spawn('python',['scripts/webtransport-receiver.py',root],{env:{...env,PYTHONPATH:resolve('artifacts/webtransport-python')},windowsHide:true,stdio:['ignore','pipe','pipe']});let errors='';python.stderr.on('data',b=>errors+=b);
const metadata=await new Promise((resolve,reject)=>{let text='';const timer=setTimeout(()=>reject(Error('WebTransport receiver startup timeout: '+errors)),15000);python.stdout.on('data',b=>{text+=b;const line=text.split('\n')[0];try{const result=JSON.parse(line);clearTimeout(timer);resolve(result);}catch{}});python.once('exit',()=>{clearTimeout(timer);reject(Error('Receiver exited: '+errors));});});
const page=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('<!doctype html><h1>Local WebTransport receiver proof</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>page.once('listening',r));
let forwarded=0;const proxy=createServer((req,res)=>{const target=new URL(req.url);if(target.hostname!=='127.0.0.1'||Number(target.port)!==page.address().port){res.writeHead(403);res.end();return;}forwarded++;const outgoing=request(target,{method:req.method,headers:req.headers},incoming=>{res.writeHead(incoming.statusCode,incoming.headers);incoming.pipe(res);});outgoing.on('error',()=>{res.writeHead(502);res.end();});req.pipe(outgoing);}).listen(0,'127.0.0.1');await new Promise(r=>proxy.once('listening',r));
const outcomes=[];
let relayAuthorizations=0;const authority={version:1},relay=await require('../apps/browser/local-dlp-relay.cjs').startLocalDlpRelay({authorize:async()=>{relayAuthorizations++;return authority;},isCurrent:a=>a===authority,resolveUpstream:async()=> 'DIRECT',resolveAddress:async()=> '127.0.0.1'});
try{
for(const mode of ['baseline','disable-quic','disable-blink','forced-proxy','private-relay']){
const before=await (await fetch(`http://127.0.0.1:${metadata.statsPort}/facts`)).json(),output=join(root,mode+'.json');
const forwardedBefore=forwarded;
const credentials=relay.credentials(),relayBefore=relayAuthorizations;
const child=spawn(require('electron'),['scripts/webtransport-native-probe.cjs'],{env:{...env,SAFETYNET_WT_MODE:mode,SAFETYNET_WT_USERNAME:credentials.username,SAFETYNET_WT_PASSWORD:credentials.password,SAFETYNET_WT_PROXY:mode==='private-relay'?relay.proxyRules:`127.0.0.1:${proxy.address().port}`,SAFETYNET_WT_TARGET:`https://127.0.0.1:${metadata.quicPort}/webtransport`,SAFETYNET_WT_PAGE:`http://127.0.0.1:${page.address().port}/`,SAFETYNET_WT_HASH:metadata.hash,SAFETYNET_WT_OUTPUT:output},windowsHide:true,stdio:['ignore','pipe','pipe']});let log='';child.stdout.on('data',b=>log+=b);child.stderr.on('data',b=>log+=b);const timer=setTimeout(()=>child.kill(),20000),code=await new Promise(r=>child.once('exit',r));clearTimeout(timer);if(code!==0)throw Error(`Native ${mode} exited ${code}: ${log}`);
const after=await (await fetch(`http://127.0.0.1:${metadata.statsPort}/facts`)).json(),result=JSON.parse(readFileSync(output,'utf8'));outcomes.push({...result,forwardedHTTP:mode==='private-relay'?relayAuthorizations-relayBefore:forwarded-forwardedBefore,receiver:{connections:after.connections-before.connections,datagrams:after.datagrams-before.datagrams,streams:after.streams-before.streams,bytes:after.bytes-before.bytes,workerBytes:after.workerBytes-before.workerBytes,packets:after.packets-before.packets,markerReceived:after.bytes>before.bytes&&after.marker}});
}
for(const result of outcomes){assert.equal(result.ordinaryPageLoaded,true);if(['baseline','disable-quic','disable-blink'].includes(result.mode)){assert.equal(result.sent,true);assert.equal(result.worker.sent,true);assert.ok(result.receiver.bytes>=132);assert.ok(result.receiver.workerBytes>=66);assert.equal(result.webRequestEvents,0);}else{assert.equal(result.sent,false);assert.equal(result.worker.sent,false);assert.equal(result.receiver.packets,0);assert.equal(result.receiver.bytes,0);assert.ok(result.forwardedHTTP>0);}}
const report={actualElectron:true,localPinnedReceiver:true,serverVersion:metadata.aioquic,outcomes};writeFileSync('artifacts/webtransport-native-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));
}finally{page.closeAllConnections();proxy.closeAllConnections();await Promise.all([new Promise(r=>page.close(r)),new Promise(r=>proxy.close(r)),relay.close()]);python.kill();}