Merge pull request #3431 from SillyTavern/ipv6_auto Better IPv6 support

acf71fd702783c1507ad952a8ed0781125c34391

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +272 -37Ignore whitespace
default/config.yaml+2 -0
@@ -7,6 +7,8 @@ cardsCacheCapacity: 100
7# Listen for incoming connections7# Listen for incoming connections
8listen: false8listen: false
9# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!9# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!
10# - Use option "auto" to automatically detect support
11# - Use true or false (no qoutes) to enable or disable each protocol
10protocol:12protocol:
11 ipv4: true13 ipv4: true
12 ipv6: false14 ipv6: false
server.js+205 -37
@@ -4,6 +4,7 @@
4import fs from 'node:fs';4import fs from 'node:fs';
5import http from 'node:http';5import http from 'node:http';
6import https from 'node:https';6import https from 'node:https';
7import os from 'os';
7import path from 'node:path';8import path from 'node:path';
8import util from 'node:util';9import util from 'node:util';
9import net from 'node:net';10import net from 'node:net';
@@ -65,6 +66,9 @@ import {
65 forwardFetchResponse,66 forwardFetchResponse,
66 removeColorFormatting,67 removeColorFormatting,
67 getSeparator,68 getSeparator,
69 stringToBool,
70 urlHostnameToIPv6,
71 canResolve,
68 safeReadFileSync,72 safeReadFileSync,
69 setupLogLevel,73 setupLogLevel,
70} from './src/util.js';74} from './src/util.js';
@@ -150,11 +154,11 @@ const DEFAULT_PROXY_BYPASS = [];
150const cliArguments = yargs(hideBin(process.argv))154const cliArguments = yargs(hideBin(process.argv))
151 .usage('Usage: <your-start-script> <command> [options]')155 .usage('Usage: <your-start-script> <command> [options]')
152 .option('enableIPv6', {156 .option('enableIPv6', {
153 type: 'boolean',157 type: 'string',
154 default: null,158 default: null,
155 describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`,159 describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`,
156 }).option('enableIPv4', {160 }).option('enableIPv4', {
157 type: 'boolean',161 type: 'string',
158 default: null,162 default: null,
159 describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`,163 describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`,
160 }).option('port', {164 }).option('port', {
@@ -243,27 +247,42 @@ app.use(helmet({
243app.use(compression());247app.use(compression());
244app.use(responseTime());248app.use(responseTime());
245249
250
251/** @type {number} */
246const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);252const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
253/** @type {boolean} */
247const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;254const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
255/** @type {boolean} */
248const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);256const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
257/** @type {boolean} */
249const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);258const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
250const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);259const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
260/** @type {string} */
251const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');261const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
262/** @type {boolean} */
252const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);263const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
253const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);264const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
254const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);265const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
266/** @type {boolean} */
255const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);267const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
256268
257const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY);269const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY);
258270
259const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
260const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
261271
272/** @type {boolean | "auto"} */
273let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
274/** @type {boolean | "auto"} */
275let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
276
277/** @type {string} */
262const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);278const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
279/** @type {number} */
263const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);280const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
264281
282/** @type {boolean} */
265const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);283const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
266284
285/** @type {boolean} */
267const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);286const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
268287
269const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);288const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);
@@ -280,7 +299,19 @@ if (dnsPreferIPv6) {
280 console.log('Preferring IPv4 for DNS resolution');299 console.log('Preferring IPv4 for DNS resolution');
281}300}
282301
283if (!enableIPv6 && !enableIPv4) {302
303const ipOptions = [true, 'auto', false];
304
305if (!ipOptions.includes(enableIPv6)) {
306 console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV6);
307 enableIPv6 = DEFAULT_ENABLE_IPV6;
308}
309if (!ipOptions.includes(enableIPv4)) {
310 console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV4);
311 enableIPv4 = DEFAULT_ENABLE_IPV4;
312}
313
314if (enableIPv6 === false && enableIPv4 === false) {
284 console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');315 console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');
285 process.exit(1);316 process.exit(1);
286}317}
@@ -365,6 +396,55 @@ function getSessionCookieAge() {
365 return undefined;396 return undefined;
366}397}
367398
399/**
400 * Checks the network interfaces to determine the presence of IPv6 and IPv4 addresses.
401 *
402 * @returns {Promise<[boolean, boolean, boolean, boolean]>} A promise that resolves to an array containing:
403 * - [0]: `hasIPv6` (boolean) - Whether the computer has any IPv6 address, including (`::1`).
404 * - [1]: `hasIPv4` (boolean) - Whether the computer has any IPv4 address, including (`127.0.0.1`).
405 * - [2]: `hasIPv6Local` (boolean) - Whether the computer has local IPv6 address (`::1`).
406 * - [3]: `hasIPv4Local` (boolean) - Whether the computer has local IPv4 address (`127.0.0.1`).
407 */
408async function getHasIP() {
409 let hasIPv6 = false;
410 let hasIPv6Local = false;
411
412 let hasIPv4 = false;
413 let hasIPv4Local = false;
414
415 const interfaces = os.networkInterfaces();
416
417 for (const iface of Object.values(interfaces)) {
418 if (iface === undefined) {
419 continue;
420 }
421
422 for (const info of iface) {
423 if (info.family === 'IPv6') {
424 hasIPv6 = true;
425 if (info.address === '::1') {
426 hasIPv6Local = true;
427 }
428 }
429
430 if (info.family === 'IPv4') {
431 hasIPv4 = true;
432 if (info.address === '127.0.0.1') {
433 hasIPv4Local = true;
434 }
435 }
436 if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break;
437 }
438 if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break;
439 }
440 return [
441 hasIPv6,
442 hasIPv4,
443 hasIPv6Local,
444 hasIPv4Local,
445 ];
446}
447
368app.use(cookieSession({448app.use(cookieSession({
369 name: getCookieSessionName(),449 name: getCookieSessionName(),
370 sameSite: 'strict',450 sameSite: 'strict',
@@ -694,21 +774,23 @@ const preSetupTasks = async function () {
694774
695/**775/**
696 * Gets the hostname to use for autorun in the browser.776 * Gets the hostname to use for autorun in the browser.
697 * @returns {string} The hostname to use for autorun777 * @param {boolean} useIPv6 If use IPv6
778 * @param {boolean} useIPv4 If use IPv4
779 * @returns Promise<string> The hostname to use for autorun
698 */780 */
699function getAutorunHostname() {781async function getAutorunHostname(useIPv6, useIPv4) {
700 if (autorunHostname === 'auto') {782 if (autorunHostname === 'auto') {
701 if (enableIPv6 && enableIPv4) {783 let localhostResolve = await canResolve('localhost', useIPv6, useIPv4);
702 return avoidLocalhost784
703 ? '[::1]'785 if (useIPv6 && useIPv4) {
704 : 'localhost';786 return (avoidLocalhost || !localhostResolve) ? '[::1]' : 'localhost';
705 }787 }
706788
707 if (enableIPv6) {789 if (useIPv6) {
708 return '[::1]';790 return '[::1]';
709 }791 }
710792
711 if (enableIPv4) {793 if (useIPv4) {
712 return '127.0.0.1';794 return '127.0.0.1';
713 }795 }
714 }796 }
@@ -720,11 +802,13 @@ function getAutorunHostname() {
720 * Tasks that need to be run after the server starts listening.802 * Tasks that need to be run after the server starts listening.
721 * @param {boolean} v6Failed If the server failed to start on IPv6803 * @param {boolean} v6Failed If the server failed to start on IPv6
722 * @param {boolean} v4Failed If the server failed to start on IPv4804 * @param {boolean} v4Failed If the server failed to start on IPv4
805 * @param {boolean} useIPv6 If the server is using IPv6
806 * @param {boolean} useIPv4 If the server is using IPv4
723 */807 */
724const postSetupTasks = async function (v6Failed, v4Failed) {808const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) {
725 const autorunUrl = new URL(809 const autorunUrl = new URL(
726 (cliArguments.ssl ? 'https://' : 'http://') +810 (cliArguments.ssl ? 'https://' : 'http://') +
727 (getAutorunHostname()) +811 (await getAutorunHostname(useIPv6, useIPv4)) +
728 (':') +812 (':') +
729 ((autorunPortOverride >= 0) ? autorunPortOverride : server_port),813 ((autorunPortOverride >= 0) ? autorunPortOverride : server_port),
730 );814 );
@@ -737,12 +821,16 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
737821
738 let logListen = 'SillyTavern is listening on';822 let logListen = 'SillyTavern is listening on';
739823
740 if (enableIPv6 && !v6Failed) {824 if (useIPv6 && !v6Failed) {
741 logListen += color.green(' IPv6: ' + tavernUrlV6.host);825 logListen += color.green(
826 ' IPv6: ' + tavernUrlV6.host,
827 );
742 }828 }
743829
744 if (enableIPv4 && !v4Failed) {830 if (useIPv4 && !v4Failed) {
745 logListen += color.green(' IPv4: ' + tavernUrl.host);831 logListen += color.green(
832 ' IPv4: ' + tavernUrl.host,
833 );
746 }834 }
747835
748 const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern';836 const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern';
@@ -754,16 +842,22 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
754 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');842 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
755843
756 if (listen) {844 if (listen) {
757 console.log('[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n');845 console.log(
846 '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n',
847 );
758 }848 }
759849
760 if (basicAuthMode) {850 if (basicAuthMode) {
761 if (perUserBasicAuth && !enableAccounts) {851 if (perUserBasicAuth && !enableAccounts) {
762 console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.'));852 console.error(color.red(
853 'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.',
854 ));
763 } else if (!perUserBasicAuth) {855 } else if (!perUserBasicAuth) {
764 const basicAuthUser = getConfigValue('basicAuthUser', {});856 const basicAuthUser = getConfigValue('basicAuthUser', {});
765 if (!basicAuthUser?.username || !basicAuthUser?.password) {857 if (!basicAuthUser?.username || !basicAuthUser?.password) {
766 console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));858 console.warn(color.yellow(
859 'Basic Authentication is enabled, but username or password is not set or empty!',
860 ));
767 }861 }
768 }862 }
769 }863 }
@@ -818,14 +912,16 @@ function logSecurityAlert(message) {
818 * Handles the case where the server failed to start on one or both protocols.912 * Handles the case where the server failed to start on one or both protocols.
819 * @param {boolean} v6Failed If the server failed to start on IPv6913 * @param {boolean} v6Failed If the server failed to start on IPv6
820 * @param {boolean} v4Failed If the server failed to start on IPv4914 * @param {boolean} v4Failed If the server failed to start on IPv4
915 * @param {boolean} useIPv6 If use IPv6
916 * @param {boolean} useIPv4 If use IPv4
821 */917 */
822function handleServerListenFail(v6Failed, v4Failed) {918function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) {
823 if (v6Failed && !enableIPv4) {919 if (v6Failed && !useIPv4) {
824 console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled'));920 console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled'));
825 process.exit(1);921 process.exit(1);
826 }922 }
827923
828 if (v4Failed && !enableIPv6) {924 if (v4Failed && !useIPv6) {
829 console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled'));925 console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled'));
830 process.exit(1);926 process.exit(1);
831 }927 }
@@ -839,10 +935,11 @@ function handleServerListenFail(v6Failed, v4Failed) {
839/**935/**
840 * Creates an HTTPS server.936 * Creates an HTTPS server.
841 * @param {URL} url The URL to listen on937 * @param {URL} url The URL to listen on
938 * @param {number} ipVersion the ip version to use
842 * @returns {Promise<void>} A promise that resolves when the server is listening939 * @returns {Promise<void>} A promise that resolves when the server is listening
843 * @throws {Error} If the server fails to start940 * @throws {Error} If the server fails to start
844 */941 */
845function createHttpsServer(url) {942function createHttpsServer(url, ipVersion) {
846 return new Promise((resolve, reject) => {943 return new Promise((resolve, reject) => {
847 const server = https.createServer(944 const server = https.createServer(
848 {945 {
@@ -851,34 +948,56 @@ function createHttpsServer(url) {
851 }, app);948 }, app);
852 server.on('error', reject);949 server.on('error', reject);
853 server.on('listening', resolve);950 server.on('listening', resolve);
854 server.listen(Number(url.port || 443), url.hostname);951
952 let host = url.hostname;
953 if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname);
954 server.listen({
955 host: host,
956 port: Number(url.port || 443),
957 // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used
958 ipv6Only: true,
959 });
855 });960 });
856}961}
857962
858/**963/**
859 * Creates an HTTP server.964 * Creates an HTTP server.
860 * @param {URL} url The URL to listen on965 * @param {URL} url The URL to listen on
966 * @param {number} ipVersion the ip version to use
861 * @returns {Promise<void>} A promise that resolves when the server is listening967 * @returns {Promise<void>} A promise that resolves when the server is listening
862 * @throws {Error} If the server fails to start968 * @throws {Error} If the server fails to start
863 */969 */
864function createHttpServer(url) {970function createHttpServer(url, ipVersion) {
865 return new Promise((resolve, reject) => {971 return new Promise((resolve, reject) => {
866 const server = http.createServer(app);972 const server = http.createServer(app);
867 server.on('error', reject);973 server.on('error', reject);
868 server.on('listening', resolve);974 server.on('listening', resolve);
869 server.listen(Number(url.port || 80), url.hostname);975
976 let host = url.hostname;
977 if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname);
978 server.listen({
979 host: host,
980 port: Number(url.port || 80),
981 // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used
982 ipv6Only: true,
983 });
870 });984 });
871}985}
872986
873async function startHTTPorHTTPS() {987/**
988 * Starts the server using http or https depending on config
989 * @param {boolean} useIPv6 If use IPv6
990 * @param {boolean} useIPv4 If use IPv4
991 */
992async function startHTTPorHTTPS(useIPv6, useIPv4) {
874 let v6Failed = false;993 let v6Failed = false;
875 let v4Failed = false;994 let v4Failed = false;
876995
877 const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer;996 const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer;
878997
879 if (enableIPv6) {998 if (useIPv6) {
880 try {999 try {
881 await createFunc(tavernUrlV6);1000 await createFunc(tavernUrlV6, 6);
882 } catch (error) {1001 } catch (error) {
883 console.error('non-fatal error: failed to start server on IPv6');1002 console.error('non-fatal error: failed to start server on IPv6');
884 console.error(error);1003 console.error(error);
@@ -887,9 +1006,9 @@ async function startHTTPorHTTPS() {
887 }1006 }
888 }1007 }
8891008
890 if (enableIPv4) {1009 if (useIPv4) {
891 try {1010 try {
892 await createFunc(tavernUrl);1011 await createFunc(tavernUrl, 4);
893 } catch (error) {1012 } catch (error) {
894 console.error('non-fatal error: failed to start server on IPv4');1013 console.error('non-fatal error: failed to start server on IPv4');
895 console.error(error);1014 console.error(error);
@@ -902,10 +1021,59 @@ async function startHTTPorHTTPS() {
902}1021}
9031022
904async function startServer() {1023async function startServer() {
905 const [v6Failed, v4Failed] = await startHTTPorHTTPS();1024 let useIPv6 = (enableIPv6 === true);
1025 let useIPv4 = (enableIPv4 === true);
1026
1027 let hasIPv6 = false,
1028 hasIPv4 = false,
1029 hasIPv6Local = false,
1030 hasIPv4Local = false,
1031 hasIPv6Any = false,
1032 hasIPv4Any = false;
1033
1034 if (enableIPv6 === 'auto' || enableIPv4 === 'auto') {
1035 [hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP();
1036
1037 hasIPv6 = listen ? hasIPv6Any : hasIPv6Local;
1038 if (enableIPv6 === 'auto') {
1039 useIPv6 = hasIPv6;
1040 }
1041 if (hasIPv6) {
1042 if (useIPv6) {
1043 console.log(color.green('IPv6 support detected'));
1044 } else {
1045 console.log('IPv6 support detected (but disabled)');
1046 }
1047 }
1048
1049 hasIPv4 = listen ? hasIPv4Any : hasIPv4Local;
1050 if (enableIPv4 === 'auto') {
1051 useIPv4 = hasIPv4;
1052 }
1053 if (hasIPv4) {
1054 if (useIPv4) {
1055 console.log(color.green('IPv4 support detected'));
1056 } else {
1057 console.log('IPv4 support detected (but disabled)');
1058 }
1059 }
1060
1061 if (enableIPv6 === 'auto' && enableIPv4 === 'auto') {
1062 if (!hasIPv6 && !hasIPv4) {
1063 console.error('Both IPv6 and IPv4 are not detected');
1064 process.exit(1);
1065 }
1066 }
1067 }
1068
1069 if (!useIPv6 && !useIPv4) {
1070 console.error('Both IPv6 and IPv4 are disabled,\nP.S. you should never see this error, at least at one point it was checked for before this, with the rest of the config options');
1071 process.exit(1);
1072 }
9061073
907 handleServerListenFail(v6Failed, v4Failed);1074 const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4);
908 postSetupTasks(v6Failed, v4Failed);1075 handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4);
1076 postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4);
909}1077}
9101078
911async function verifySecuritySettings() {1079async function verifySecuritySettings() {
src/util.js+65 -0
@@ -5,6 +5,7 @@ import process from 'node:process';
5import { Readable } from 'node:stream';5import { Readable } from 'node:stream';
6import { createRequire } from 'node:module';6import { createRequire } from 'node:module';
7import { Buffer } from 'node:buffer';7import { Buffer } from 'node:buffer';
8import { promises as dnsPromise } from 'node:dns';
89
9import yaml from 'yaml';10import yaml from 'yaml';
10import { sync as commandExistsSync } from 'command-exists';11import { sync as commandExistsSync } from 'command-exists';
@@ -695,6 +696,70 @@ export function isValidUrl(url) {
695}696}
696697
697/**698/**
699 * removes starting `[` or ending `]` from hostname.
700 * @param {string} hostname hostname to use
701 * @returns {string} hostname plus the modifications
702 */
703export function urlHostnameToIPv6(hostname) {
704 if (hostname.startsWith('[')) {
705 hostname = hostname.slice(1);
706 }
707 if (hostname.endsWith(']')) {
708 hostname = hostname.slice(0, -1);
709 }
710 return hostname;
711}
712
713/**
714 * Test if can resolve a dns name.
715 * @param {string} name Domain name to use
716 * @param {boolean} useIPv6 If use IPv6
717 * @param {boolean} useIPv4 If use IPv4
718 * @returns Promise<boolean> If the URL is valid
719 */
720export async function canResolve(name, useIPv6 = true, useIPv4 = true) {
721 try {
722 let v6Resolved = false;
723 let v4Resolved = false;
724
725 if (useIPv6) {
726 try {
727 await dnsPromise.resolve6(name);
728 v6Resolved = true;
729 } catch (error) {
730 v6Resolved = false;
731 }
732 }
733
734 if (useIPv4) {
735 try {
736 await dnsPromise.resolve(name);
737 v4Resolved = true;
738 } catch (error) {
739 v4Resolved = false;
740 }
741 }
742
743 return v6Resolved || v4Resolved;
744
745 } catch (error) {
746 return false;
747 }
748}
749
750
751/**
752 * converts string to boolean accepts 'true' or 'false' else it returns the string put in
753 * @param {string|null} str Input string or null
754 * @returns {boolean|string|null} boolean else original input string or null if input is
755 */
756export function stringToBool(str) {
757 if (str === 'true') return true;
758 if (str === 'false') return false;
759 return str;
760}
761
762/**
698 * Setup the minimum log level763 * Setup the minimum log level
699 */764 */
700export function setupLogLevel() {765export function setupLogLevel() {