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