diff --git a/p2ns.js b/p2ns.js index 0342efe..fefb34e 100644 --- a/p2ns.js +++ b/p2ns.js @@ -1,4 +1,3 @@ -require('dotenv').config(); // Load environment variables const { exec } = require('child_process'); const dgram = require('dgram'); const dnsPacket = require('dns-packet'); @@ -9,16 +8,11 @@ const http = require('http'); const b4a = require('b4a'); const { createHash } = require('crypto'); const net = require('net'); +const os = require('os'); // Corestore and Hyperswarm setup for P2P sync const store = new Corestore('./my-storage'); const swarm = new Hyperswarm(); - -// Option for setting a customizable masterNetwork_discoveryKey for TLD syncing -const masterNetworkDiscoveryKey = process.env.masterNetworkDiscoveryKey - ? Buffer.from(process.env.masterNetworkDiscoveryKey, 'hex') - : null; - const dnsCore = store.get({ name: 'dns-core' }); let holesailClient = null; @@ -26,8 +20,6 @@ const dnsServer = dgram.createSocket('udp4'); const domainToIPMap = {}; let currentIP = 2; // Start assigning IP addresses from 192.168.100.2 -console.log(`Loaded masterNetworkDiscoveryKey: ${masterNetworkDiscoveryKey ? masterNetworkDiscoveryKey.toString('hex') : 'None'}`); - // Helper function to remove existing virtual interface if it already exists (for macOS) function removeExistingInterface(subnetName) { return new Promise((resolve) => { @@ -110,7 +102,7 @@ function checkPublicDNS(domain) { questions: [{ type: 'A', name: domain }] }); - resolver.send(query, 53, '1.1.1.1', (err) => { + resolver.send(query, 53, '192.168.0.16', (err) => { if (err) { logDebug(`Error forwarding DNS query to 1.1.1.1: ${err}`); return resolve(null); @@ -130,45 +122,44 @@ function checkPublicDNS(domain) { }); } -const activeConnections = {}; // Store active connections by domain - // Function to start Holesail client for tunneling and use port 80 with SO_REUSEADDR function startHolesailClient(domain, hash, ip, port) { - logDebug(`Attempting to start/reuse Holesail client for domain: ${domain}`); - - if (activeConnections[domain]) { - logDebug(`Reusing existing Holesail client for domain: ${domain} on ${ip}:${port}`); - return activeConnections[domain]; // Reuse the existing connection - } - - logDebug(`Starting new Holesail client for domain: ${domain}, hash: ${hash}, IP: ${ip}, Port: ${port}`); + logDebug(`Starting Holesail client for domain: ${domain}, hash: ${hash}, IP: ${ip}, Port: ${port}`); const connector = setupConnector(hash); - const holesailClient = new HolesailClient(connector); + holesailClient = new HolesailClient(connector); holesailClient.connect({ port: port, address: ip, reuseAddr: true }, () => { logDebug(`Holesail client for ${domain} connected on ${ip}:${port}`); }); - // Store the client in activeConnections - activeConnections[domain] = holesailClient; - - // Set a timeout to destroy and remove from activeConnections after 5 minutes (300,000 ms) setTimeout(() => { - logDebug(`Destroying Holesail client for domain ${domain}`); + logDebug(`Destroying Holesail client for ${domain}`); holesailClient.destroy(); - delete activeConnections[domain]; // Remove the client from activeConnections }, 300000); +} - return holesailClient; +// Function to restart Holesail client if the port is unresponsive +async function restartHolesailClient(domain, hash, ip, port) { + const isResponsive = await checkPortResponsive(ip, port); + + if (!isResponsive) { + logDebug(`Port ${port} on ${ip} is unresponsive, destroying and recreating the Holesail client`); + if (holesailClient) { + holesailClient.destroy(); + } + + await createInterfaceForDomain(domain); + startHolesailClient(domain, hash, ip, port); + } else { + logDebug(`Port ${port} on ${ip} is responsive, using the existing connection.`); + } } // Ensure the DNS Core is ready before joining the swarm (async () => { await dnsCore.ready(); - - // Use masterNetworkDiscoveryKey if provided; otherwise, use dnsCore.discoveryKey - const topic = masterNetworkDiscoveryKey || dnsCore.discoveryKey; + const topic = dnsCore.discoveryKey; logDebug(`DNS Core ready, joining Hyperswarm with topic: ${topic.toString('hex')}`); @@ -190,5 +181,130 @@ async function addDomain(domain, hash) { logDebug(`Domain ${domain} added to DNS core`); } +// Function to handle long connectors (128-byte) or standard connectors (64-byte) +function setupConnector(keyInput) { + if (keyInput.length === 64) { + logDebug(`Using 64-byte connector: ${keyInput}`); + return keyInput; + } else { + logDebug(`Hashing 128-byte connector: ${keyInput}`); + const connector = createHash('sha256').update(keyInput.toString()).digest('hex'); + const seed = Buffer.from(connector, 'hex'); + return b4a.toString(seed, 'hex'); + } +} + +// DNS Server Setup for listening on port 53 +dnsServer.on('message', async (msg, rinfo) => { + const query = dnsPacket.decode(msg); + const domain = query.questions[0].name; + const type = query.questions[0].type; + + logDebug(`DNS query received: Domain = ${domain}, Type = ${type}`); + + await dnsCore.ready(); + let p2pRecord = null; + + for await (const data of dnsCore.createReadStream()) { + const record = JSON.parse(data.toString()); + if (record.domain === domain) { + p2pRecord = record; + break; + } + } + + const publicDNSRecords = await checkPublicDNS(domain); + + if (p2pRecord) { + const localIP = domainToIPMap[domain] || await createInterfaceForDomain(domain); + startHolesailClient(domain, p2pRecord.hash, localIP, 80); + + const response = dnsPacket.encode({ + type: 'response', + id: query.id, + questions: query.questions, + answers: [{ + type: 'A', + name: domain, + ttl: 300, + data: localIP + }] + }); + dnsServer.send(response, rinfo.port, rinfo.address); + } else if (publicDNSRecords) { + const response = dnsPacket.encode({ + type: 'response', + id: query.id, + questions: query.questions, + answers: publicDNSRecords + }); + dnsServer.send(response, rinfo.port, rinfo.address); + } else { + logDebug(`No P2P or public DNS records found for ${domain}`); + const response = dnsPacket.encode({ + type: 'response', + id: query.id, + questions: query.questions, + answers: [] + }); + dnsServer.send(response, rinfo.port, rinfo.address); + } +}); + +// Ensure DNS server binds to port 53 with elevated permissions (may require sudo) +dnsServer.bind(53, '0.0.0.0', (err) => { + if (err) { + console.error(`Failed to bind DNS server to port 53: ${err.message}`); + process.exit(1); + } else { + logDebug('DNS Server running on port 53, bound to 0.0.0.0'); + } +}); + +// HTTP Server with DNS query before proxying +http.createServer(async (req, res) => { + const domain = req.url.replace("/", ""); + logDebug(`HTTP request for domain: ${domain}`); + + try { + const localIP = domainToIPMap[domain] || await createInterfaceForDomain(domain); + + if (!localIP) { + logDebug(`No DNS records for ${domain}`); + res.writeHead(404); + return res.end('Domain not found'); + } + + await restartHolesailClient(domain, '78b94094b5ee1e352fec1f37e9cf3780561bcaa6ef9e0678100c9283888f', localIP, 80); + + const options = { + hostname: localIP, + port: 80, + path: req.url, + method: req.method, + headers: req.headers, + }; + + const proxyRequest = http.request(options, (proxyRes) => { + res.writeHead(proxyRes.statusCode, proxyRes.headers); + proxyRes.pipe(res, { end: true }); + }); + + proxyRequest.on('error', (err) => { + logDebug(`Error proxying request for ${domain}: ${err.message}`); + res.writeHead(500); + res.end('Internal Server Error'); + }); + + req.pipe(proxyRequest, { end: true }); + } catch (err) { + logDebug(`Failed to handle request for ${domain}: ${err.message}`); + res.writeHead(500); + res.end('Internal Server Error'); + } +}).listen(80, '127.0.0.1', () => { + logDebug('HTTP server running on port 80'); +}); + // Example: Add a domain to the P2P DNS system -addDomain('hello.geek', '07b8b52fbbad7a89ce26ad2d8375e6a82b2e3c02f18596bddff18e9c31164b04'); +addDomain('hello.geek', '78b94094b5ee1e352fec1f37e9cf3780561bcaa6ef9e0678100c9283888f');