feat: wip implementation of dns: {...} stanza handling

backend/services/network.js implements correct handling of dns: stanza
required by ZeroNSd.

Unfortunately this breaks frontend.
I think this is because of delete statements.
Unfortunately I know too little about nodejs to implement a proper solution.
I tried mitigating problem using a copy of the data, but that didn't work.
Anything else coming to mind seems to ugly to be 'right'.
This commit is contained in:
Mauro Condarelli 2021-10-09 10:15:13 +02:00
commit 0b77e9594b
3 changed files with 163 additions and 70 deletions

View file

@ -4,7 +4,7 @@ const axios = require("axios");
const api = require("../utils/controller-api"); const api = require("../utils/controller-api");
const db = require("../utils/db"); const db = require("../utils/db");
const constants = require("../utils/constants"); const constants = require("../utils/constants");
const startStopDNS = require("../utils/zns"); const zns = require("../utils/zns");
async function getNetworkAdditionalData(data) { async function getNetworkAdditionalData(data) {
let additionalData = db let additionalData = db
@ -22,11 +22,22 @@ async function getNetworkAdditionalData(data) {
delete data.remoteTraceLevel; delete data.remoteTraceLevel;
delete data.remoteTraceTarget; delete data.remoteTraceTarget;
let ad = { ...additionalData.value() };
data.dns = {
domain: ad.dnsDomain,
servers: ["pippo"],
};
if (ad.dnsIP) data.dns["servers"].push(ad.dnsIP);
console.log(`*** dns="${JSON.stringify(ad)}" -> ${JSON.stringify(data.dns)}`);
delete ad.dnsIP;
delete ad.dnsDomain;
delete ad.dnsEnable;
delete ad.dnsWildcard;
return { return {
id: data.id, id: data.id,
type: "Network", type: "Network",
clock: Math.floor(new Date().getTime() / 1000), clock: Math.floor(new Date().getTime() / 1000),
...additionalData.value(), ...ad,
config: data, config: data,
}; };
} }
@ -47,13 +58,11 @@ async function getNetworksData(nwids) {
return []; return [];
}); });
let data = Promise.all( return Promise.all(
multipleRes.map((el) => { multipleRes.map((el) => {
return getNetworkAdditionalData(el.data); return getNetworkAdditionalData(el.data);
}) })
); );
return data;
} }
exports.createNetworkAdditionalData = createNetworkAdditionalData; exports.createNetworkAdditionalData = createNetworkAdditionalData;
@ -84,6 +93,12 @@ async function updateNetworkAdditionalData(nwid, data) {
additionalData.rulesSource = data.rulesSource; additionalData.rulesSource = data.rulesSource;
} }
if (data.hasOwnProperty("dnsEnable")) { if (data.hasOwnProperty("dnsEnable")) {
if (data.dnsEnable) {
//TODO: start ZeroNSd and get its IP address
additionalData.dnsIP = "127.0.0.1";
} else {
additionalData.dnsIP = null;
}
additionalData.dnsEnable = data.dnsEnable; additionalData.dnsEnable = data.dnsEnable;
} }
if (data.hasOwnProperty("dnsDomain")) { if (data.hasOwnProperty("dnsDomain")) {
@ -101,17 +116,7 @@ async function updateNetworkAdditionalData(nwid, data) {
.write(); .write();
if (data.hasOwnProperty("dnsEnable")) { if (data.hasOwnProperty("dnsEnable")) {
let token = db.get("users").value()[0]; zns.handleNet(db.get("networks").filter({ id: nwid }).value()[0]);
token = token.token;
let config = db.get("networks").find({ id: nwid }).value();
let ret = startStopDNS(token, config.id, config.additionalConfig);
if (ret) {
db.get("networks")
.filter({ id: nwid })
.map("additionalConfig")
.map((additionalConfig) => _.assign(additionalConfig, ret))
.write();
}
} }
} }
} }

View file

@ -1,64 +1,147 @@
const { spawn } = require("child_process"); const path = require("path");
const fs = require("fs");
function pidIsRunning(pid) { const db = require("../utils/db");
try { const cp = require("child_process");
process.kill(pid, 0);
return true; //TODO: does this kind of "optimization" make sense in Node.js?
} catch (e) { let token = null;
return false; function getToken() {
} if (!token)
try {
token = db.get("users").value()[0].token;
} catch {
console.warn("*** token retrieval failed");
}
return token;
} }
function setPid(nwid, pid) {
db.get("networks")
.filter({ id: nwid })
.map("additionalConfig")
.map((additionalConfig) => (additionalConfig.pidDNS = pid))
.write();
}
const isRunning = (query, pid) => {
return new Promise(function (resolve) {
//FIXME: Check if pgrep is available
cp.exec(`pgrep ${query}`, (err, stdout) => {
resolve(stdout.indexOf(`${pid}`) > -1);
});
});
};
function startDNS(token, nwid, conf) { function startDNS(token, nwid, conf) {
if (conf.hasOwnProperty("pidDNS")) { //FIXME: check it does the right thing when conf.pidDNS is null/undefined
if (pidIsRunning(conf.pidDNS)) { isRunning("zeronsd", conf.pidDNS).then((ok) => {
return null; if (ok) {
console.log(
`startDNS(${token}, ${nwid}): already active on PID ${conf.pidDNS}`
);
} else {
let cmd = "zeronsd";
let opts = Array();
if (process.geteuid() === 0) {
// production in Docker container
} else {
// we are debugging
let myLocal = "/home/mcon/.cargo/bin";
let pth = process.env.PATH.split(path.delimiter);
if (!pth.includes(myLocal)) pth.push(myLocal);
if (
!process.env.PATH.split(path.delimiter).some(function (d) {
let e = path.resolve(d, cmd);
console.log(`*** PATH testing: "${d}" -> "${e}"`);
try {
fs.accessSync(e, fs.constants.X_OK);
console.log(" is executable");
cmd = "sudo";
opts.push("-E", e);
return true;
} catch (e) {
console.warn(` cannot execute (${e})`);
return false;
}
})
) {
console.error(`*** zeronsd not found in PATH (${process.env.PATH})`);
return;
}
}
opts.push("start", "-v", "-v");
if (conf.hasOwnProperty("dnsWildcard") && conf.dnsWildcard) {
opts.push("-w");
}
if (conf.hasOwnProperty("dnsDomain") && !!conf.dnsDomain) {
opts.push("-d", conf.dnsDomain);
}
opts.push(nwid);
process.env.ZEROTIER_CENTRAL_TOKEN = token;
console.log(`*** PATH: "${process.env.PATH}"`);
console.log(
`*** ZEROTIER_CENTRAL_TOKEN: "${process.env.ZEROTIER_CENTRAL_TOKEN}"`
);
let dns = cp.spawn(cmd, opts, { detached: true });
dns.on("spawn", () => {
console.log(
`zeronsd successfully spawned [${dns.pid}](${dns.spawnargs})`
);
setPid(nwid, dns.pid);
});
dns.stdout.on("data", (data) => {
console.log(`zeronsd spawn stdout: ${data}`);
});
dns.stderr.on("data", (data) => {
console.error(`zeronsd spawn stderr: ${data}`);
});
dns.on("error", (error) => {
console.log(`zeronsd spawn ERROR: [${error}](${dns.spawnargs})`);
});
dns.on("close", (code) => {
console.log(`zeronsd exited: [${code}](${dns.spawnargs})`);
setPid(nwid, null);
});
} }
delete conf.pidDNS;
}
const opts = Array("start");
if (conf.hasOwnProperty("dnsWildcard") && conf.dnsWildcard) {
opts.push("-w");
}
if (conf.hasOwnProperty("dnsDomain") && !!conf.dnsDomain) {
opts.push("-d", conf.dnsDomain);
}
opts.push(nwid);
let env = process.env;
env.ZEROTIER_CENTRAL_TOKEN = token;
console.log(`*** PATH: "${env.PATH}"`);
const dns = spawn("zeronsd", opts, {
detached: true,
env: env,
}); });
dns.on("spawn", () => {
console.log(`zeronsd successfully spawned [${dns.pid}](${dns.spawnargs})`);
console.log("*** should update PID ***");
});
dns.on("error", (error) => {
console.log(`zeronsd spawn ERROR: [${error}](${dns.spawnargs})`);
});
conf.pidDNS = dns.pid;
return conf;
} }
function stopDNS(conf) { function stopDNS(nwid, conf) {
if (conf.hasOwnProperty("pidDNS")) { let pid = conf.pidDNS;
try { if (pid) {
process.kill(conf.pidDNS); isRunning("zeronsd", pid).then((ok) => {
} catch (e) {} if (ok) {
delete conf.pidDNS; console.log(`stopDNS(${nwid}): stopping PID ${pid}`);
return conf; try {
} process.kill(pid);
return null; } catch (e) {
} console.error(`stopDNS(${nwid}): stopping PID ${pid} FAILED (${e})`);
}
module.exports = function (token, nwid, conf) { } else {
let ret; console.log(`stopDNS(${nwid}): PID ${pid} is stale`);
if (conf.dnsEnable) { }
ret = startDNS(token, nwid, conf); });
setPid(nwid, null);
} else { } else {
ret = stopDNS(conf); console.log(`stopDNS(${nwid}): net has no PID`);
} }
return ret; }
};
exports.handleNet = handleNet;
function handleNet(net) {
let cfg = net.additionalConfig;
if (cfg.dnsEnable) {
startDNS(getToken(), net.id, cfg);
} else {
stopDNS(net.id, cfg);
}
}
exports.scan = scan;
function scan() {
let nets = db.get("networks").value();
nets.forEach((net) => {
handleNet(net);
});
}

View file

@ -41,6 +41,11 @@ function NetworkSettings({ network, setNetwork }) {
sendReq(data); sendReq(data);
}; };
console.log(
`*** dns="${JSON.stringify(network)}" -> ${JSON.stringify(
network["config"]
)}`
);
return ( return (
<Accordion> <Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>