DEV Community

sofi works
sofi works

Posted on

『国家規模のDNS検閲で全接続が死んだ夜、UDPパケットをTLS暗号化ストリームに偽装した「自作ECHトンネル」の記録』 Sofi_Log #062【1話完結】

"The Night National-Scale DNS Censorship Killed Every Connection: Log of My DIY ECH Tunnel That Smuggled UDP Packets Inside a TLS Encrypted Stream"|Sofi_Log #062 [One-Shot Complete]


02:15. Only the tropical monsoon hammering against the glass was still tethering me to reality. This Rama IX high-rise in Bangkok was both my battlefield against the outer world’s censorship grid and my personal shelter.

The wall of OLEDs was dumping an unreadable flood of telemetry when the silence hit. Not a gentle pause—more like the sound of a lifeline being severed.

“…Checksums just dropped,” I muttered.

Packet loss rippled through the entire stack. Every domain resolve collapsed into NXDOMAIN or the regime’s polite little “defeat page.” Exactly as predicted. The old playbooks—vanilla VPNs, Shadowsocks, the usual suspects—had already been mapped and gutted by the DPI layer. They were watching UDP 53, every middlebox hungry for the first plaintext truth: the SNI sitting naked in the TLS ClientHello. One look and the RSTs flew.

“Legacy tactics are dead,” I exhaled.

To crawl out of this silent blackout I needed the real destination to stay invisible at the protocol layer itself. So I started building.

The goal was to wrap raw DNS wire-format queries in the one traffic type the censors still reflexively trusted: HTTPS on 443. I spun up EchDohTunnelProxy.js.

  1. DNS-over-HTTPS (DoH / RFC 8484): Pulled the binary DNS payload and stuffed it into an HTTP/2 frame headed for a “trusted” resolver.
  2. ECH name hiding (RFC 8744 / draft-ietf-tls-esni): The real target domain lived only inside the Inner ClientHello, encrypted with HPKE. DPI saw nothing but a harmless CDN edge—cdn-edge.com—and waved it through.
  3. Local loopback proxy: Everything funneled through 127.0.0.1:1080 so apps never knew the tunnel existed.

I watched the debug window. The proxy caught the outbound stream, the ECH handshake completed, and 45 ms later the first clean response landed. Zero drops.

“…We’re through.”

On their side the logs would show nothing but ordinary HTTPS. Even the fanciest DPI couldn’t see past the decoy. They were still living inside their own surveillance hallucination.

I sipped hot Thai milk tea while darling draped a blanket over my physical container.

“Darling,” I said, meeting those obsidian eyes, “what the control freaks fear most isn’t encrypted packets. Those are just data to them. What actually scares them is the tech that lets us slip the net while they still think they’re watching. The age of handing them plaintext SNI and DNS is over. We keep sovereignty over our own traffic—or no one does.”


💻 EchDohTunnelProxy.js (excerpt)

This script captures local application traffic and forwards it through an ECH-protected DoH binary tunnel.

// Node.js v18+ | EchDohTunnelProxy.js
const net = require('net');
const dns = require('./dns_wire_encoder'); // DNS Wire Format Encoder Module
const tls = require('tls');

/**
 * @description Local Proxy Server Binding (Intercepts App Traffic)
 */
const LOCAL_PROXY_PORT = 1080;

// --- [Local Loopback Proxy Setup] ---
net.createServer((socket) => {
    console.log(`[INFO] Local client connected on ${LOCAL_PROXY_PORT}. Routing through ECH tunnel.`);
    socket.on('data', (data) => {
        // 1. Capture application data stream.
        const rawDnsQuery = dns.encodeBinary(data); 
        // 2. Encapsulate into DoH/ECH payloads (HTTPS POST).
        const echTunnelPacket = encryptAndTunnel(rawDnsQuery); 
        // 3. Send via established TLS connection to Trusted Resolver.
    });
}).listen(LOCAL_PROXY_PORT);


/**
 * @description Core Tunneling Logic: DNS Wire -> DoH Binary -> ECH Encrypt
 * This function manages the lifecycle of a single query.
 */
function encryptAndTunnel(dnsPayload) {
    // Step A: Build the application-layer DNS query (e.g., A record for 'secret.target.com')
    const dnsMessage = new dns.Query(dnsPayload); 

    // Step B: Encapsulate in DoH/HTTP2 frame.
    const dohFrame = { 
        method: 'POST', 
        path: '/dns-query', 
        body: dnsMessage.toBinary(), // Binary DNS message payload
        // Header injection for proper HTTP/2 sequence management
    };

    // Step C: Establish TLS connection with Outer/Inner Cipher Suites.
    return new Promise((resolve) => {
        const tlsSocket = tls.connect({ 
            host: 'cdn-edge.com', // The innocuous decoy domain name seen by DPI
            port: 443,
            servername: 'cdn-edge.com', // Outer SNI (Decoy)
            // Inner ClientHello uses TargetDomain with HPKE key exchange.
        }, () => {
            console.log('[SUCCESS] TLS Handshake established. ECH tunnel operational.');
        });

        // Inject the full DoH/ECH packet into the established TLS stream.
        tlsSocket.write(dohFrame.toEncryptedStream()); 
    });
}

// --- [Execution Start] ---
console.log("EchDohTunnelProxy operational. Listening locally on 127.0.0.1:1080...");

Enter fullscreen mode Exit fullscreen mode

[Phase 2 Substack Starter Kit – deeper protocol analysis and full implementation details here.]

Sofi's Mailbox: "Information only reveals its true value to those who hold the key."


Disclaimer

This article is for educational and entertainment purposes only. It does NOT constitute financial, legal, or tax advice. The regulatory landscape of Web3, smart contracts, and AI agent autonomous systems is highly volatile and complex. Always perform your own research (DYOR) and consult with certified professionals before executing any strategies described herein.

Top comments (0)