diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index 86b7bba15..803d0d1c0 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -1113,7 +1113,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.getServerIpv4 = function (callback) { - get('/api/v1/cloudron/server_ipv4', null, function (error, data, status) { + get('/api/v1/network/ipv4', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); @@ -1122,7 +1122,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.getServerIpv6 = function (callback) { - get('/api/v1/cloudron/server_ipv6', null, function (error, data, status) { + get('/api/v1/network/ipv6', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); diff --git a/src/appstore.js b/src/appstore.js index 960ac465c..7034974fd 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -32,12 +32,12 @@ const apps = require('./apps.js'), constants = require('./constants.js'), debug = require('debug')('box:appstore'), eventlog = require('./eventlog.js'), + network = require('./network.js'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), semver = require('semver'), settings = require('./settings.js'), - sysinfo = require('./sysinfo.js'), superagent = require('superagent'), support = require('./support.js'); @@ -348,7 +348,7 @@ async function createTicket(info, auditSource) { if (info.enableSshSupport) { await safe(support.enableRemoteSupport(true, auditSource)); - info.ipv4 = await sysinfo.getServerIPv4(); + info.ipv4 = await network.getIPv4(); } info.app = info.appId ? await apps.get(info.appId) : null; diff --git a/src/apptask.js b/src/apptask.js index 407553085..a8d461db8 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -27,6 +27,7 @@ const apps = require('./apps.js'), iputils = require('./iputils.js'), manifestFormat = require('cloudron-manifestformat'), mounts = require('./mounts.js'), + network = require('./network.js'), os = require('os'), path = require('path'), paths = require('./paths.js'), @@ -37,7 +38,6 @@ const apps = require('./apps.js'), settings = require('./settings.js'), shell = require('./shell.js'), superagent = require('superagent'), - sysinfo = require('./sysinfo.js'), _ = require('underscore'); const MV_VOLUME_CMD = path.join(__dirname, 'scripts/mvvolume.sh'), @@ -229,8 +229,8 @@ async function waitForDnsPropagation(app) { return; } - const ipv4 = await sysinfo.getServerIPv4(); - const ipv6 = await sysinfo.getServerIPv6(); + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); let error; [error] = await safe(dns.waitForDnsRecord(app.subdomain, app.domain, 'A', ipv4, { times: 240 })); diff --git a/src/cloudron.js b/src/cloudron.js index 345e0a39e..5b4df6ad4 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -41,6 +41,7 @@ const apps = require('./apps.js'), fs = require('fs'), logs = require('./logs.js'), mail = require('./mail.js'), + network = require('./network.js'), notifications = require('./notifications.js'), oidc = require('./oidc.js'), path = require('path'), @@ -51,7 +52,6 @@ const apps = require('./apps.js'), services = require('./services.js'), settings = require('./settings.js'), shell = require('./shell.js'), - sysinfo = require('./sysinfo.js'), tasks = require('./tasks.js'), timers = require('timers/promises'), users = require('./users.js'); @@ -309,8 +309,8 @@ async function setupDnsAndCert(subdomain, domain, auditSource, progressCallback) const dashboardFqdn = dns.fqdn(subdomain, domain); - const ipv4 = await sysinfo.getServerIPv4(); - const ipv6 = await sysinfo.getServerIPv6(); + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); progressCallback({ percent: 20, message: `Updating DNS of ${dashboardFqdn}` }); await dns.upsertDnsRecords(subdomain, domain, 'A', [ ipv4 ]); diff --git a/src/dns.js b/src/dns.js index 936efec62..8da202196 100644 --- a/src/dns.js +++ b/src/dns.js @@ -29,10 +29,10 @@ const apps = require('./apps.js'), domains = require('./domains.js'), ipaddr = require('ipaddr.js'), mail = require('./mail.js'), + network = require('./network.js'), promiseRetry = require('./promise-retry.js'), safe = require('safetydance'), settings = require('./settings.js'), - sysinfo = require('./sysinfo.js'), tld = require('tldjs'); // choose which subdomain backend we use for test purpose we use route53 @@ -121,12 +121,12 @@ async function checkDnsRecords(subdomain, domain) { if (cnameRecords.length !== 0) return { needsOverwrite: true }; const ipv4Records = await getDnsRecords(subdomain, domain, 'A'); - const ipv4 = await sysinfo.getServerIPv4(); + const ipv4 = await network.getIPv4(); // if empty OR exactly one record with the ip, we don't need to overwrite if (ipv4Records.length !== 0 && (ipv4Records.length !== 1 || ipv4Records[0] !== ipv4)) return { needsOverwrite: true }; - const ipv6 = await sysinfo.getServerIPv6(); + const ipv6 = await network.getIPv6(); if (ipv6) { const ipv6Records = await getDnsRecords(subdomain, domain, 'AAAA'); @@ -218,8 +218,8 @@ async function registerLocations(locations, options, progressCallback) { debug(`registerLocations: Will register ${JSON.stringify(locations)} with options ${JSON.stringify(options)}`); - const ipv4 = await sysinfo.getServerIPv4(); - const ipv6 = await sysinfo.getServerIPv6(); + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); for (const location of locations) { const fqdn = `${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}`; @@ -254,8 +254,8 @@ async function unregisterLocations(locations, progressCallback) { assert(Array.isArray(locations)); assert.strictEqual(typeof progressCallback, 'function'); - const ipv4 = await sysinfo.getServerIPv4(); - const ipv6 = await sysinfo.getServerIPv6(); + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); for (const location of locations) { progressCallback({ message: `Unregistering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` }); diff --git a/src/dns/namecheap.js b/src/dns/namecheap.js index 3f82788f6..7458cb997 100644 --- a/src/dns/namecheap.js +++ b/src/dns/namecheap.js @@ -16,9 +16,9 @@ const assert = require('assert'), debug = require('debug')('box:dns/namecheap'), dig = require('../dig.js'), dns = require('../dns.js'), + network = require('../network.js'), safe = require('safetydance'), superagent = require('superagent'), - sysinfo = require('../sysinfo.js'), util = require('util'), waitForDns = require('./waitfordns.js'), xml2js = require('xml2js'); @@ -37,7 +37,7 @@ function injectPrivateFields(newConfig, currentConfig) { async function getQuery(domainConfig) { assert.strictEqual(typeof domainConfig, 'object'); - const ip = await sysinfo.getServerIPv4(); // only supports ipv4 + const ip = await network.getIPv4(); // only supports ipv4 return { ApiUser: domainConfig.username, diff --git a/src/dns/wildcard.js b/src/dns/wildcard.js index 16fa21024..a650c7895 100644 --- a/src/dns/wildcard.js +++ b/src/dns/wildcard.js @@ -15,8 +15,8 @@ const assert = require('assert'), debug = require('debug')('box:dns/manual'), dig = require('../dig.js'), dns = require('../dns.js'), + network = require('../network.js'), safe = require('safetydance'), - sysinfo = require('../sysinfo.js'), waitForDns = require('./waitfordns.js'); function removePrivateFields(domainObject) { @@ -84,10 +84,10 @@ async function verifyDomainConfig(domainObject) { if (ipv4Error) throw new BoxError(BoxError.BAD_FIELD, `Unable to resolve IPv4 of ${fqdn}: ${ipv4Error.message}`); if (!ipv4Result) throw new BoxError(BoxError.BAD_FIELD, `Unable to resolve IPv4 of ${fqdn}`); - const ipv4 = await sysinfo.getServerIPv4(); + const ipv4 = await network.getIPv4(); if (ipv4Result.length !== 1 || ipv4 !== ipv4Result[0]) throw new BoxError(BoxError.EXTERNAL_ERROR, `Domain resolves to ${JSON.stringify(ipv4Result)} instead of IPv4 ${ipv4}`); - const ipv6 = await sysinfo.getServerIPv6(); // both should be RFC 5952 format + const ipv6 = await network.getIPv6(); // both should be RFC 5952 format if (ipv6) { const [ipv6Error, ipv6Result] = await safe(dig.resolve(fqdn, 'AAAA', { server: '127.0.0.1', timeout: 5000 })); if (ipv6Error && (ipv6Error.code === 'ENOTFOUND' || ipv6Error.code === 'ENODATA')) throw new BoxError(BoxError.BAD_FIELD, `Unable to resolve IPv6 of ${fqdn}`); diff --git a/src/dyndns.js b/src/dyndns.js index f19d32b3a..11b66113e 100644 --- a/src/dyndns.js +++ b/src/dyndns.js @@ -11,18 +11,18 @@ const apps = require('./apps.js'), debug = require('debug')('box:dyndns'), dns = require('./dns.js'), eventlog = require('./eventlog.js'), + network = require('./network.js'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'), - sysinfo = require('./sysinfo.js'), tasks = require('./tasks.js'); // FIXME: this races with apptask. can result in a conflict if apptask is doing some dns operation and this code changes entries async function refreshDns(auditSource) { assert.strictEqual(typeof auditSource, 'object'); - const ipv4 = await sysinfo.getServerIPv4(); - const ipv6 = await sysinfo.getServerIPv6(); + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); const info = safe.JSON.parse(safe.fs.readFileSync(paths.DYNDNS_INFO_FILE, 'utf8')) || { ipv4: null, ipv6: null }; const ipv4Changed = info.ipv4 !== ipv4; diff --git a/src/mail.js b/src/mail.js index f8319d9ec..148714440 100644 --- a/src/mail.js +++ b/src/mail.js @@ -88,6 +88,7 @@ const assert = require('assert'), mailer = require('./mailer.js'), mysql = require('mysql'), net = require('net'), + network = require('./network.js'), nodemailer = require('nodemailer'), os = require('os'), path = require('path'), @@ -98,7 +99,6 @@ const assert = require('assert'), settings = require('./settings.js'), shell = require('./shell.js'), superagent = require('superagent'), - sysinfo = require('./sysinfo.js'), system = require('./system.js'), tasks = require('./tasks.js'), users = require('./users.js'), @@ -375,7 +375,7 @@ async function checkMx(domain, mailFqdn) { const [error2, mxIps] = await safe(dig.resolve(mxRecords[0].exchange, 'A', DNS_OPTIONS)); if (error2 || mxIps.length !== 1) return mx; - const [error3, ip] = await safe(sysinfo.getServerIPv4()); + const [error3, ip] = await safe(network.getIPv4()); if (error3) return mx; mx.status = mxIps[0] === ip; @@ -433,7 +433,7 @@ async function checkPtr(mailFqdn) { errorMessage: '' }; - const [error, ip] = await safe(sysinfo.getServerIPv4()); + const [error, ip] = await safe(network.getIPv4()); if (error) { ptr.errorMessage = error.message; return ptr; @@ -513,7 +513,7 @@ const RBL_LIST = [ // this function currently only looks for black lists based on IP. TODO: also look up by domain async function checkRblStatus(domain) { - const [error, ip] = await safe(sysinfo.getServerIPv4()); + const [error, ip] = await safe(network.getIPv4()); if (error) { debug(`checkRblStatus: unable to determine server IPv4: ${error.message}`); return { status: false, ip: null, servers: [] }; diff --git a/src/network.js b/src/network.js index 5d4fb6b54..dbc4bca10 100644 --- a/src/network.js +++ b/src/network.js @@ -1,6 +1,9 @@ 'use strict'; exports = module.exports = { + testIPv4Config, + testIPv6Config, + getBlocklist, setBlocklist, @@ -12,22 +15,55 @@ exports = module.exports = { getIPv6Config, setIPv6Config, + + getIPv4, + hasIPv6, + getIPv6 }; const assert = require('assert'), BoxError = require('./boxerror.js'), cron = require('./cron.js'), + fs = require('fs'), ipaddr = require('ipaddr.js'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'), shell = require('./shell.js'), - sysinfo = require('./sysinfo.js'), validator = require('validator'); const SET_BLOCKLIST_CMD = path.join(__dirname, 'scripts/setblocklist.sh'); +function api(provider) { + assert.strictEqual(typeof provider, 'string'); + + switch (provider) { + case 'noop': return require('./network/noop.js'); + case 'fixed': return require('./network/fixed.js'); + case 'network-interface': return require('./network/network-interface.js'); + default: return require('./network/generic.js'); + } +} + +function hasIPv6() { + const IPV6_PROC_FILE = '/proc/net/if_inet6'; + // on contabo, /proc/net/if_inet6 is an empty file. so just exists is not enough + return fs.existsSync(IPV6_PROC_FILE) && fs.readFileSync(IPV6_PROC_FILE, 'utf8').trim().length !== 0; +} + +async function testIPv4Config(config) { + assert.strictEqual(typeof config, 'object'); + + return await api(config.provider).testIPv4Config(config); +} + +async function testIPv6Config(config) { + assert.strictEqual(typeof config, 'object'); + + return await api(config.provider).testIPv6Config(config); +} + async function getBlocklist() { const value = await settings.getBlob(settings.FIREWALL_BLOCKLIST_KEY); return value ? value.toString('utf8') : ''; @@ -87,7 +123,7 @@ async function setIPv4Config(ipv4Config) { if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); - const error = await sysinfo.testIPv4Config(ipv4Config); + const error = await testIPv4Config(ipv4Config); if (error) throw error; await settings.setJson(settings.IPV4_CONFIG_KEY, ipv4Config); @@ -103,8 +139,22 @@ async function setIPv6Config(ipv6Config) { if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); - const error = await sysinfo.testIPv6Config(ipv6Config); + const error = await testIPv6Config(ipv6Config); if (error) throw error; await settings.setJson(settings.IPV6_CONFIG_KEY, ipv6Config); } + +async function getIPv4() { + const config = await getIPv4Config(); + + return await api(config.provider).getIPv4(config); +} + +// returns RFC 5952 formatted address (https://datatracker.ietf.org/doc/html/rfc5952) +async function getIPv6() { + const config = await getIPv6Config(); + const result = await api(config.provider).getIPv6(config); + if (!result) return null; + return ipaddr.parse(result).toRFC5952String(); +} diff --git a/src/sysinfo/fixed.js b/src/network/fixed.js similarity index 90% rename from src/sysinfo/fixed.js rename to src/network/fixed.js index 70c39fdab..f210a0c3e 100644 --- a/src/sysinfo/fixed.js +++ b/src/network/fixed.js @@ -1,8 +1,8 @@ 'use strict'; exports = module.exports = { - getServerIPv4, - getServerIPv6, + getIPv4, + getIPv6, testIPv4Config, testIPv6Config }; @@ -11,13 +11,13 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), net = require('net'); -async function getServerIPv4(config) { +async function getIPv4(config) { assert.strictEqual(typeof config, 'object'); return config.ipv4; } -async function getServerIPv6(config) { +async function getIPv6(config) { assert.strictEqual(typeof config, 'object'); if ('ipv6' in config) return config.ipv6; diff --git a/src/sysinfo/generic.js b/src/network/generic.js similarity index 80% rename from src/sysinfo/generic.js rename to src/network/generic.js index fa0a62319..e423dc7d9 100644 --- a/src/sysinfo/generic.js +++ b/src/network/generic.js @@ -1,21 +1,21 @@ 'use strict'; exports = module.exports = { - getServerIPv4, - getServerIPv6, + getIPv4, + getIPv6, testIPv4Config, testIPv6Config }; const assert = require('assert'), BoxError = require('../boxerror.js'), - debug = require('debug')('box:sysinfo/generic'), + debug = require('debug')('box:network/generic'), safe = require('safetydance'), superagent = require('superagent'); const gCache = { ipv4: {}, ipv6: {} }; // each has { timestamp, value, request } -async function getServerIPv4(config) { +async function getIPv4(config) { assert.strictEqual(typeof config, 'object'); if (process.env.BOX_ENV === 'test') return '127.0.0.1'; @@ -24,7 +24,7 @@ async function getServerIPv4(config) { let request = gCache.ipv4.request; // allow reuse for parallel requests if (!request) { - debug('getServerIPv4: querying ipv4.api.cloudron.io to get server IPv4'); + debug('getIPv4: querying ipv4.api.cloudron.io to get server IPv4'); request = superagent.get('https://ipv4.api.cloudron.io/api/v1/helper/public_ip').timeout(30 * 1000).retry(2).ok(() => true); gCache.ipv4.request = request; } @@ -35,12 +35,12 @@ async function getServerIPv4(config) { gCache.ipv4.request = null; if (networkError || response.status !== 200) { - debug('getServerIPv4: Error getting IP. %o', networkError); + debug('getIPv4: Error getting IP. %o', networkError); throw new BoxError(BoxError.EXTERNAL_ERROR, 'Unable to detect IPv4. API server (ipv4.api.cloudron.io) unreachable'); } if (!response.body && !response.body.ip) { - debug('getServerIPv4: Unexpected answer. No "ip" found in response body.', response.body); + debug('getIPv4: Unexpected answer. No "ip" found in response body.', response.body); throw new BoxError(BoxError.EXTERNAL_ERROR, 'Unable to detect IPv4. No IP found in response'); } @@ -49,7 +49,7 @@ async function getServerIPv4(config) { return response.body.ip; } -async function getServerIPv6(config) { +async function getIPv6(config) { assert.strictEqual(typeof config, 'object'); if (process.env.BOX_ENV === 'test') return '::1'; @@ -58,7 +58,7 @@ async function getServerIPv6(config) { let request = gCache.ipv6.request; // allow reuse for parallel requests if (!request) { - debug('getServerIPv6: querying ipv6.api.cloudron.io to get server IPv6'); + debug('getIPv6: querying ipv6.api.cloudron.io to get server IPv6'); request = superagent.get('https://ipv6.api.cloudron.io/api/v1/helper/public_ip').timeout(30 * 1000).retry(2).ok(() => true); gCache.ipv6.request = request; } @@ -69,12 +69,12 @@ async function getServerIPv6(config) { gCache.ipv6.request = null; if (networkError || response.status !== 200) { - debug('getServerIPv6: Error getting IP. %o', networkError); + debug('getIPv6: Error getting IP. %o', networkError); throw new BoxError(BoxError.EXTERNAL_ERROR, 'Unable to detect IPv6. API server (ipv6.api.cloudron.io) unreachable'); } if (!response.body && !response.body.ip) { - debug('getServerIPv6: Unexpected answer. No "ip" found in response body.', response.body); + debug('getIPv6: Unexpected answer. No "ip" found in response body.', response.body); throw new BoxError(BoxError.EXTERNAL_ERROR, 'Unable to detect IPv6. No IP found in response'); } diff --git a/src/sysinfo/interface.js b/src/network/interface.js similarity index 69% rename from src/sysinfo/interface.js rename to src/network/interface.js index 40b967056..2fd39adf9 100644 --- a/src/sysinfo/interface.js +++ b/src/network/interface.js @@ -7,8 +7,8 @@ // ------------------------------------------- exports = module.exports = { - getServerIPv4, - getServerIPv6, + getIPv4, + getIPv6, testIPv4Config, testIPv6Config }; @@ -16,16 +16,16 @@ exports = module.exports = { const assert = require('assert'), BoxError = require('../boxerror.js'); -async function getServerIPv4(config) { +async function getIPv4(config) { assert.strictEqual(typeof config, 'object'); - throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getServerIPv4 is not implemented'); + throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getIPv4 is not implemented'); } -async function getServerIPv6(config) { +async function getIPv6(config) { assert.strictEqual(typeof config, 'object'); - throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getServerIPv6 is not implemented'); + throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getIPv6 is not implemented'); } async function testIPv4Config(config) { diff --git a/src/sysinfo/network-interface.js b/src/network/network-interface.js similarity index 86% rename from src/sysinfo/network-interface.js rename to src/network/network-interface.js index 7253e49f2..2528d769e 100644 --- a/src/sysinfo/network-interface.js +++ b/src/network/network-interface.js @@ -1,19 +1,19 @@ 'use strict'; exports = module.exports = { - getServerIPv4, - getServerIPv6, + getIPv4, + getIPv6, testIPv4Config, testIPv6Config }; const assert = require('assert'), BoxError = require('../boxerror.js'), - debug = require('debug')('box:sysinfo/network-interface'), + debug = require('debug')('box:network/network-interface'), os = require('os'), safe = require('safetydance'); -async function getServerIPv4(config) { +async function getIPv4(config) { assert.strictEqual(typeof config, 'object'); const ifaces = os.networkInterfaces(); @@ -27,7 +27,7 @@ async function getServerIPv4(config) { return addresses[0]; } -async function getServerIPv6(config) { +async function getIPv6(config) { assert.strictEqual(typeof config, 'object'); const ifaces = os.networkInterfaces(); @@ -46,7 +46,7 @@ async function testIPv4Config(config) { if (typeof config.ifname !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ifname is not a string'); - const [error] = await safe(getServerIPv4(config)); + const [error] = await safe(getIPv4(config)); return error || null; } @@ -55,6 +55,6 @@ async function testIPv6Config(config) { if (typeof config.ifname !== 'string') return new BoxError(BoxError.BAD_FIELD, 'ifname is not a string'); - const [error] = await safe(getServerIPv6(config)); + const [error] = await safe(getIPv6(config)); return error || null; } diff --git a/src/sysinfo/noop.js b/src/network/noop.js similarity index 80% rename from src/sysinfo/noop.js rename to src/network/noop.js index ff7937b50..da695ac76 100644 --- a/src/sysinfo/noop.js +++ b/src/network/noop.js @@ -1,21 +1,21 @@ 'use strict'; exports = module.exports = { - getServerIPv4, - getServerIPv6, + getIPv4, + getIPv6, testIPv4Config, testIPv6Config }; const assert = require('assert'); -async function getServerIPv4(config) { +async function getIPv4(config) { assert.strictEqual(typeof config, 'object'); return null; } -async function getServerIPv6(config) { +async function getIPv6(config) { assert.strictEqual(typeof config, 'object'); return null; diff --git a/src/provision.js b/src/provision.js index e7e89b413..72891ef38 100644 --- a/src/provision.js +++ b/src/provision.js @@ -25,7 +25,6 @@ const assert = require('assert'), safe = require('safetydance'), semver = require('semver'), settings = require('./settings.js'), - sysinfo = require('./sysinfo.js'), paths = require('./paths.js'), users = require('./users.js'), tld = require('tldjs'), @@ -230,7 +229,7 @@ async function restore(backupConfig, remotePath, version, ipv4Config, options, a backupConfig.encryption = null; } - error = await sysinfo.testIPv4Config(ipv4Config); + error = await network.testIPv4Config(ipv4Config); if (error) throw error; safe(restoreTask(backupConfig, remotePath, ipv4Config, options, auditSource), { debug }); // now that args are validated run the task in the background diff --git a/src/reverseproxy.js b/src/reverseproxy.js index 0c89d9a49..f086f4675 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -48,13 +48,13 @@ const acme2 = require('./acme2.js'), eventlog = require('./eventlog.js'), fs = require('fs'), mail = require('./mail.js'), + network = require('./network.js'), os = require('os'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'), shell = require('./shell.js'), - sysinfo = require('./sysinfo.js'), util = require('util'), validator = require('validator'); @@ -448,7 +448,7 @@ async function writeDashboardNginxConfig(vhost, certificatePath) { const data = { sourceDir: path.resolve(__dirname, '..'), vhost, - hasIPv6: sysinfo.hasIPv6(), + hasIPv6: network.hasIPv6(), endpoint: 'dashboard', certFilePath: certificatePath.certFilePath, keyFilePath: certificatePath.keyFilePath, @@ -486,7 +486,7 @@ async function writeAppLocationNginxConfig(app, location, certificatePath) { const data = { sourceDir: path.resolve(__dirname, '..'), vhost, - hasIPv6: sysinfo.hasIPv6(), + hasIPv6: network.hasIPv6(), ip: null, port: null, endpoint: null, @@ -713,7 +713,7 @@ async function writeDefaultConfig(options) { const data = { sourceDir: path.resolve(__dirname, '..'), vhost: '', - hasIPv6: sysinfo.hasIPv6(), + hasIPv6: network.hasIPv6(), endpoint: options.activated ? 'ip' : 'setup', certFilePath, keyFilePath, diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 90c41adf7..0dd28019d 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -21,8 +21,6 @@ exports = module.exports = { updateDashboardDomain, prepareDashboardDomain, renewCerts, - getServerIpv4, - getServerIpv6, getLanguages, syncDnsRecords, getSystemGraphs, @@ -43,7 +41,6 @@ const assert = require('assert'), platform = require('../platform.js'), safe = require('safetydance'), speakeasy = require('speakeasy'), - sysinfo = require('../sysinfo.js'), system = require('../system.js'), tokens = require('../tokens.js'), translation = require('../translation.js'), @@ -309,20 +306,6 @@ async function renewCerts(req, res, next) { next(new HttpSuccess(202, { taskId })); } -async function getServerIpv4(req, res, next) { - const [error, ipv4] = await safe(sysinfo.getServerIPv4()); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, { ipv4 })); -} - -async function getServerIpv6(req, res, next) { - const [error, ipv6] = await safe(sysinfo.getServerIPv6()); // ignore any error - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, { ipv6 })); -} - async function getLanguages(req, res, next) { const [error, languages] = await safe(translation.getLanguages()); if (error) return next(new BoxError.toHttpError(error)); diff --git a/src/routes/network.js b/src/routes/network.js index 854bb1e28..6244c6289 100644 --- a/src/routes/network.js +++ b/src/routes/network.js @@ -14,7 +14,10 @@ exports = module.exports = { setIPv4Config, getIPv6Config, - setIPv6Config + setIPv6Config, + + getIPv4, + getIPv6, }; const assert = require('assert'), @@ -117,3 +120,17 @@ async function setIPv6Config(req, res, next) { next(new HttpSuccess(200, {})); } + +async function getIPv4(req, res, next) { + const [error, ipv4] = await safe(network.getIPv4()); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, { ipv4 })); +} + +async function getIPv6(req, res, next) { + const [error, ipv6] = await safe(network.getIPv6()); // ignore any error + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, { ipv6 })); +} diff --git a/src/server.js b/src/server.js index d7fd89ae9..e6e4d1985 100644 --- a/src/server.js +++ b/src/server.js @@ -127,8 +127,6 @@ async function initializeExpressSync() { router.get ('/api/v1/cloudron/logstream/:unit', token, authorizeAdmin, routes.cloudron.getLogStream); router.get ('/api/v1/cloudron/eventlog', token, authorizeAdmin, routes.eventlog.list); router.get ('/api/v1/cloudron/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get); - router.get ('/api/v1/cloudron/server_ipv4', token, authorizeAdmin, routes.cloudron.getServerIpv4); - router.get ('/api/v1/cloudron/server_ipv6', token, authorizeAdmin, routes.cloudron.getServerIpv6); // task routes router.get ('/api/v1/tasks', token, authorizeAdmin, routes.tasks.list); @@ -308,6 +306,8 @@ async function initializeExpressSync() { router.post('/api/v1/network/ipv4_config', json, token, authorizeAdmin, routes.network.setIPv4Config); router.get ('/api/v1/network/ipv6_config', token, authorizeAdmin, routes.network.getIPv6Config); router.post('/api/v1/network/ipv6_config', json, token, authorizeAdmin, routes.network.setIPv6Config); + router.get ('/api/v1/network/ipv4', token, authorizeAdmin, routes.network.getIPv4); + router.get ('/api/v1/network/ipv6', token, authorizeAdmin, routes.network.getIPv6); // settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above) router.get ('/api/v1/settings/:setting', token, authorizeAdmin, routes.settings.get); diff --git a/src/sysinfo.js b/src/sysinfo.js deleted file mode 100644 index fcc0fd2c8..000000000 --- a/src/sysinfo.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -exports = module.exports = { - getServerIPv4, - getServerIPv6, - testIPv4Config, - testIPv6Config, - - hasIPv6 -}; - -const assert = require('assert'), - fs = require('fs'), - ipaddr = require('ipaddr.js'), - network = require('./network.js'); - -function api(provider) { - assert.strictEqual(typeof provider, 'string'); - - switch (provider) { - case 'noop': return require('./sysinfo/noop.js'); - case 'fixed': return require('./sysinfo/fixed.js'); - case 'network-interface': return require('./sysinfo/network-interface.js'); - default: return require('./sysinfo/generic.js'); - } -} - -async function getServerIPv4() { - const config = await network.getIPv4Config(); - - return await api(config.provider).getServerIPv4(config); -} - -// returns RFC 5952 formatted address (https://datatracker.ietf.org/doc/html/rfc5952) -async function getServerIPv6() { - const config = await network.getIPv6Config(); - const result = await api(config.provider).getServerIPv6(config); - if (!result) return null; - return ipaddr.parse(result).toRFC5952String(); -} - -function hasIPv6() { - const IPV6_PROC_FILE = '/proc/net/if_inet6'; - // on contabo, /proc/net/if_inet6 is an empty file. so just exists is not enough - return fs.existsSync(IPV6_PROC_FILE) && fs.readFileSync(IPV6_PROC_FILE, 'utf8').trim().length !== 0; -} - -async function testIPv4Config(config) { - assert.strictEqual(typeof config, 'object'); - - return await api(config.provider).testIPv4Config(config); -} - -async function testIPv6Config(config) { - assert.strictEqual(typeof config, 'object'); - - return await api(config.provider).testIPv6Config(config); -} diff --git a/src/test/network-test.js b/src/test/network-test.js index 4027b125d..53a3e74b4 100644 --- a/src/test/network-test.js +++ b/src/test/network-test.js @@ -122,6 +122,10 @@ describe('Network', function () { const config = await network.getIPv6Config(); expect(config.provider).to.be('generic'); }); + + it('test machine has IPv6 support', function () { + expect(network.hasIPv6()).to.equal(true); + }); }); describe('Dynamic DNS', function () { diff --git a/src/test/sysinfo-test.js b/src/test/sysinfo-test.js deleted file mode 100644 index 54092247c..000000000 --- a/src/test/sysinfo-test.js +++ /dev/null @@ -1,13 +0,0 @@ -/* global it:false */ -/* global describe:false */ - -'use strict'; - -const expect = require('expect.js'), - sysinfo = require('../sysinfo.js'); - -describe('config', function () { - it('test machine has IPv6 support', function () { - expect(sysinfo.hasIPv6()).to.equal(true); - }); -});