p2ns/p2ns.js
2024-11-09 09:15:23 -05:00

311 lines
10 KiB
JavaScript

const { exec } = require('child_process');
const dgram = require('dgram');
const dnsPacket = require('dns-packet');
const HolesailClient = require('holesail-client');
const Corestore = require('corestore');
const Hyperswarm = require('hyperswarm');
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();
const dnsCore = store.get({ name: 'dns-core' });
let holesailClient = null;
const dnsServer = dgram.createSocket('udp4');
const domainToIPMap = {};
let currentIP = 2; // Start assigning IP addresses from 192.168.100.2
// Helper function to remove existing virtual interface if it already exists (for macOS)
function removeExistingInterface(subnetName) {
return new Promise((resolve) => {
exec(`sudo ifconfig ${subnetName} down`, (err) => {
if (err) resolve();
else {
console.log(`Removed existing virtual interface: ${subnetName}`);
resolve();
}
});
});
}
// Helper function to create virtual interfaces (for macOS)
async function createVirtualInterface(subnetName, subnetCIDR) {
await removeExistingInterface(subnetName);
return new Promise((resolve, reject) => {
exec(`sudo ifconfig ${subnetName} alias ${subnetCIDR}`, (err, stdout, stderr) => {
if (err) {
console.error(`Error creating virtual interface ${subnetName}:`, stderr);
reject(`Error creating virtual interface ${subnetName}: ${stderr}`);
} else {
console.log(`Virtual interface ${subnetName} created with CIDR ${subnetCIDR}.`);
resolve(subnetCIDR);
}
});
});
}
// Updated function to create virtual interfaces dynamically, starting from 192.168.100.2
async function createInterfaceForDomain(domain) {
const subnetID = currentIP;
const subnetName = `lo0`;
const subnetCIDR = `192.168.100.${subnetID}/24`;
try {
await createVirtualInterface(subnetName, subnetCIDR);
domainToIPMap[domain] = `192.168.100.${subnetID}`;
logDebug(`Assigned virtual interface IP: 192.168.100.${subnetID} for domain: ${domain}`);
currentIP++;
return domainToIPMap[domain];
} catch (error) {
console.error(error);
return null;
}
}
// Debug log wrapper for standardized output
function logDebug(info) {
const timestamp = new Date().toISOString();
console.log(`[DEBUG ${timestamp}] ${info}`);
}
// Function to check if a port is responsive
function checkPortResponsive(ip, port) {
return new Promise((resolve) => {
const client = new net.Socket();
client.setTimeout(2000);
client.connect(port, ip, () => {
client.destroy();
resolve(true);
});
client.on('error', () => resolve(false));
client.on('timeout', () => {
client.destroy();
resolve(false);
});
});
}
// Function to check public DNS using Cloudflare
function checkPublicDNS(domain) {
return new Promise((resolve) => {
const resolver = dgram.createSocket('udp4');
const query = dnsPacket.encode({
type: 'query',
id: Math.floor(Math.random() * 65535),
questions: [{ type: 'A', name: domain }]
});
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);
}
});
resolver.on('message', (res) => {
const response = dnsPacket.decode(res);
if (response.answers.length > 0) {
logDebug(`Public DNS returned records for ${domain}`);
resolve(response.answers);
} else {
resolve(null);
}
resolver.close();
});
});
}
// Function to start Holesail client for tunneling and use port 80 with SO_REUSEADDR
function startHolesailClient(domain, hash, ip, port) {
logDebug(`Starting Holesail client for domain: ${domain}, hash: ${hash}, IP: ${ip}, Port: ${port}`);
const connector = setupConnector(hash);
holesailClient = new HolesailClient(connector);
holesailClient.connect({ port: port, address: ip, reuseAddr: true }, () => {
logDebug(`Holesail client for ${domain} connected on ${ip}:${port}`);
});
setTimeout(() => {
logDebug(`Destroying Holesail client for ${domain}`);
holesailClient.destroy();
}, 300000);
}
// 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();
const topic = dnsCore.discoveryKey;
logDebug(`DNS Core ready, joining Hyperswarm with topic: ${topic.toString('hex')}`);
swarm.join(topic, { server: true, client: true });
swarm.on('connection', (conn) => {
logDebug('Peer connected, starting replication...');
dnsCore.replicate(conn);
});
})();
// Function to add a domain and hash to the DNS core (P2P network)
async function addDomain(domain, hash) {
await dnsCore.ready();
const record = JSON.stringify({ domain, hash });
logDebug(`Adding domain ${domain} with hash ${hash} to DNS core`);
await dnsCore.append(Buffer.from(record));
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', '78b94094b5ee1e352fec1f37e9cf3780561bcaa6ef9e0678100c9283888f');