added automatic ipv6 ipv4 detection
| @@ -7,9 +7,11 @@ cardsCacheCapacity: 100 | |||
| 7 | # Listen for incoming connections | 7 | # Listen for incoming connections |
| 8 | listen: false | 8 | listen: 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 "enabled" or "disabled" to enable or disabled each protocol | ||
| 10 | protocol: | 12 | protocol: |
| 11 | ipv4: true | 13 | ipv4: auto |
| 12 | ipv6: false | 14 | ipv6: auto |
| 13 | # Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6 | 15 | # Prefers IPv6 for DNS. Enable this on ISPs that don't have issues with IPv6 |
| 14 | dnsPreferIPv6: false | 16 | dnsPreferIPv6: false |
| 15 | # The hostname that autorun opens. | 17 | # The hostname that autorun opens. |
| @@ -4,6 +4,7 @@ | |||
| 4 | import fs from 'node:fs'; | 4 | import fs from 'node:fs'; |
| 5 | import http from 'node:http'; | 5 | import http from 'node:http'; |
| 6 | import https from 'node:https'; | 6 | import https from 'node:https'; |
| 7 | import os from 'os'; | ||
| 7 | import path from 'node:path'; | 8 | import path from 'node:path'; |
| 8 | import util from 'node:util'; | 9 | import util from 'node:util'; |
| 9 | import net from 'node:net'; | 10 | import net from 'node:net'; |
| @@ -133,8 +134,8 @@ const DEFAULT_CSRF_DISABLED = false; | |||
| 133 | const DEFAULT_BASIC_AUTH = false; | 134 | const DEFAULT_BASIC_AUTH = false; |
| 134 | const DEFAULT_PER_USER_BASIC_AUTH = false; | 135 | const DEFAULT_PER_USER_BASIC_AUTH = false; |
| 135 | 136 | ||
| 136 | const DEFAULT_ENABLE_IPV6 = false; | 137 | const DEFAULT_ENABLE_IPV6 = "auto"; |
| 137 | const DEFAULT_ENABLE_IPV4 = true; | 138 | const DEFAULT_ENABLE_IPV4 = "auto"; |
| 138 | 139 | ||
| 139 | const DEFAULT_PREFER_IPV6 = false; | 140 | const DEFAULT_PREFER_IPV6 = false; |
| 140 | 141 | ||
| @@ -149,11 +150,11 @@ const DEFAULT_PROXY_BYPASS = []; | |||
| 149 | 150 | ||
| 150 | const cliArguments = yargs(hideBin(process.argv)) | 151 | const cliArguments = yargs(hideBin(process.argv)) |
| 151 | .usage('Usage: <your-start-script> <command> [options]') | 152 | .usage('Usage: <your-start-script> <command> [options]') |
| 152 | .option('enableIPv6', { | 153 | .option('string', { |
| 153 | type: 'boolean', | 154 | type: 'boolean', |
| 154 | default: null, | 155 | default: null, |
| 155 | describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`, | 156 | describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`, |
| 156 | }).option('enableIPv4', { | 157 | }).option('string', { |
| 157 | type: 'boolean', | 158 | type: 'boolean', |
| 158 | default: null, | 159 | default: null, |
| 159 | describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`, | 160 | describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`, |
| @@ -280,7 +281,7 @@ if (dnsPreferIPv6) { | |||
| 280 | console.log('Preferring IPv4 for DNS resolution'); | 281 | console.log('Preferring IPv4 for DNS resolution'); |
| 281 | } | 282 | } |
| 282 | 283 | ||
| 283 | if (!enableIPv6 && !enableIPv4) { | 284 | if (enableIPv6 == "disabled" && enableIPv4 == "disabled") { |
| 284 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); | 285 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); |
| 285 | process.exit(1); | 286 | process.exit(1); |
| 286 | } | 287 | } |
| @@ -365,6 +366,28 @@ function getSessionCookieAge() { | |||
| 365 | return undefined; | 366 | return undefined; |
| 366 | } | 367 | } |
| 367 | 368 | ||
| 369 | async function getHasIP() { | ||
| 370 | let hasIPv6 = false; | ||
| 371 | let hasIPv4 = false; | ||
| 372 | const interfaces = os.networkInterfaces(); | ||
| 373 | |||
| 374 | for (const iface of Object.values(interfaces)) { | ||
| 375 | if (iface === undefined) { | ||
| 376 | continue | ||
| 377 | } | ||
| 378 | for (const info of iface) { | ||
| 379 | if (info.family === 'IPv6') { | ||
| 380 | hasIPv6 = true; | ||
| 381 | } | ||
| 382 | |||
| 383 | if (info.family === 'IPv4') { | ||
| 384 | hasIPv4 = true; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | } | ||
| 388 | return [hasIPv6, hasIPv4]; | ||
| 389 | } | ||
| 390 | |||
| 368 | app.use(cookieSession({ | 391 | app.use(cookieSession({ |
| 369 | name: getCookieSessionName(), | 392 | name: getCookieSessionName(), |
| 370 | sameSite: 'strict', | 393 | sameSite: 'strict', |
| @@ -685,18 +708,18 @@ const preSetupTasks = async function () { | |||
| 685 | * Gets the hostname to use for autorun in the browser. | 708 | * Gets the hostname to use for autorun in the browser. |
| 686 | * @returns {string} The hostname to use for autorun | 709 | * @returns {string} The hostname to use for autorun |
| 687 | */ | 710 | */ |
| 688 | function getAutorunHostname() { | 711 | function getAutorunHostname(useIPv6, useIPv4) { |
| 689 | if (autorunHostname === 'auto') { | 712 | if (autorunHostname === 'auto') { |
| 690 | if (enableIPv6 && enableIPv4) { | 713 | if (useIPv6 && useIPv4) { |
| 691 | if (avoidLocalhost) return '[::1]'; | 714 | if (avoidLocalhost) return '[::1]'; |
| 692 | return 'localhost'; | 715 | return 'localhost'; |
| 693 | } | 716 | } |
| 694 | 717 | ||
| 695 | if (enableIPv6) { | 718 | if (useIPv6) { |
| 696 | return '[::1]'; | 719 | return '[::1]'; |
| 697 | } | 720 | } |
| 698 | 721 | ||
| 699 | if (enableIPv4) { | 722 | if (useIPv4) { |
| 700 | return '127.0.0.1'; | 723 | return '127.0.0.1'; |
| 701 | } | 724 | } |
| 702 | } | 725 | } |
| @@ -709,10 +732,10 @@ function getAutorunHostname() { | |||
| 709 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 732 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 710 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 733 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 711 | */ | 734 | */ |
| 712 | const postSetupTasks = async function (v6Failed, v4Failed) { | 735 | const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { |
| 713 | const autorunUrl = new URL( | 736 | const autorunUrl = new URL( |
| 714 | (cliArguments.ssl ? 'https://' : 'http://') + | 737 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 715 | (getAutorunHostname()) + | 738 | (getAutorunHostname(useIPv6, useIPv4)) + |
| 716 | (':') + | 739 | (':') + |
| 717 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), | 740 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), |
| 718 | ); | 741 | ); |
| @@ -725,11 +748,11 @@ const postSetupTasks = async function (v6Failed, v4Failed) { | |||
| 725 | 748 | ||
| 726 | let logListen = 'SillyTavern is listening on'; | 749 | let logListen = 'SillyTavern is listening on'; |
| 727 | 750 | ||
| 728 | if (enableIPv6 && !v6Failed) { | 751 | if (useIPv6 && !v6Failed) { |
| 729 | logListen += color.green(' IPv6: ' + tavernUrlV6.host); | 752 | logListen += color.green(' IPv6: ' + tavernUrlV6.host); |
| 730 | } | 753 | } |
| 731 | 754 | ||
| 732 | if (enableIPv4 && !v4Failed) { | 755 | if (useIPv4 && !v4Failed) { |
| 733 | logListen += color.green(' IPv4: ' + tavernUrl.host); | 756 | logListen += color.green(' IPv4: ' + tavernUrl.host); |
| 734 | } | 757 | } |
| 735 | 758 | ||
| @@ -805,13 +828,13 @@ function logSecurityAlert(message) { | |||
| 805 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 828 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 806 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 829 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 807 | */ | 830 | */ |
| 808 | function handleServerListenFail(v6Failed, v4Failed) { | 831 | function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { |
| 809 | if (v6Failed && !enableIPv4) { | 832 | if (v6Failed && !useIPv4) { |
| 810 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); | 833 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); |
| 811 | process.exit(1); | 834 | process.exit(1); |
| 812 | } | 835 | } |
| 813 | 836 | ||
| 814 | if (v4Failed && !enableIPv6) { | 837 | if (v4Failed && !useIPv6) { |
| 815 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); | 838 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); |
| 816 | process.exit(1); | 839 | process.exit(1); |
| 817 | } | 840 | } |
| @@ -856,13 +879,13 @@ function createHttpServer(url) { | |||
| 856 | }); | 879 | }); |
| 857 | } | 880 | } |
| 858 | 881 | ||
| 859 | async function startHTTPorHTTPS() { | 882 | async function startHTTPorHTTPS(useIPv6, useIPv4) { |
| 860 | let v6Failed = false; | 883 | let v6Failed = false; |
| 861 | let v4Failed = false; | 884 | let v4Failed = false; |
| 862 | 885 | ||
| 863 | const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer; | 886 | const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer; |
| 864 | 887 | ||
| 865 | if (enableIPv6) { | 888 | if (useIPv6) { |
| 866 | try { | 889 | try { |
| 867 | await createFunc(tavernUrlV6); | 890 | await createFunc(tavernUrlV6); |
| 868 | } catch (error) { | 891 | } catch (error) { |
| @@ -873,7 +896,7 @@ async function startHTTPorHTTPS() { | |||
| 873 | } | 896 | } |
| 874 | } | 897 | } |
| 875 | 898 | ||
| 876 | if (enableIPv4) { | 899 | if (useIPv4) { |
| 877 | try { | 900 | try { |
| 878 | await createFunc(tavernUrl); | 901 | await createFunc(tavernUrl); |
| 879 | } catch (error) { | 902 | } catch (error) { |
| @@ -888,10 +911,32 @@ async function startHTTPorHTTPS() { | |||
| 888 | } | 911 | } |
| 889 | 912 | ||
| 890 | async function startServer() { | 913 | async function startServer() { |
| 891 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(); | 914 | let useIPv6 = (enableIPv6 == "enabled") |
| 915 | let useIPv4 = (enableIPv4 == "enabled") | ||
| 916 | |||
| 917 | const [hasIPv6, hasIPv4] = await getHasIP() | ||
| 918 | if (enableIPv6 == "auto") { | ||
| 919 | useIPv6 = hasIPv6; | ||
| 920 | if (useIPv6) { | ||
| 921 | console.log("IPv6 support detected") | ||
| 922 | } | ||
| 923 | } | ||
| 924 | |||
| 925 | if (enableIPv4 == "auto") { | ||
| 926 | useIPv4 = hasIPv4; | ||
| 927 | if (useIPv4) { | ||
| 928 | console.log("IPv4 support detected") | ||
| 929 | } | ||
| 930 | } | ||
| 931 | |||
| 932 | if (!useIPv6 && !useIPv4) { | ||
| 933 | console.log("No IPv6 and no IPv4 enabled") | ||
| 934 | } | ||
| 935 | |||
| 936 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4); | ||
| 892 | 937 | ||
| 893 | handleServerListenFail(v6Failed, v4Failed); | 938 | handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4); |
| 894 | postSetupTasks(v6Failed, v4Failed); | 939 | postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4); |
| 895 | } | 940 | } |
| 896 | 941 | ||
| 897 | async function verifySecuritySettings() { | 942 | async function verifySecuritySettings() { |