diff --git a/admin/src/OptionsPage.ts b/admin/src/OptionsPage.ts index eab90f1b..39f506a2 100644 --- a/admin/src/OptionsPage.ts +++ b/admin/src/OptionsPage.ts @@ -117,7 +117,19 @@ export default function OptionsPage() { helperText: "Not applied to localhost. Doesn't work with proxies." }, - { k: 'listen_interface', comp: SelectField, sm: 4, options: [{ label: "any", value: '' }, '127.0.0.1', '::1', ...status?.ips||[]] }, + { + k: 'listen_interface', + comp: SelectField, + sm: 4, + options: [ + { label: "any", value: '' }, + { label: "any IPv4", value: '0.0.0.0' }, + { label: "any IPv6", value: '::' }, + '127.0.0.1', + '::1', + ...status?.ips || [] + ] + }, { k: 'max_kbps', ...maxSpeedDefaults, sm: 4, label: "Limit output", helperText: "Doesn't apply to localhost" }, { k: 'max_kbps_per_ip', ...maxSpeedDefaults, sm: 4, label: "Limit output per-IP" }, diff --git a/src/listen.ts b/src/listen.ts index 1b8eb749..61dad9ff 100644 --- a/src/listen.ts +++ b/src/listen.ts @@ -8,7 +8,7 @@ import { watchLoad } from './watchLoad' import { networkInterfaces } from 'os'; import { newConnection } from './connections' import open from 'open' -import { debounceAsync, ipForUrl, makeNetMatcher, MINUTE, objSameKeys, onlyTruthy, prefix, runAt, wait, } from './misc' +import { debounceAsync, ipForUrl, makeNetMatcher, MINUTE, objSameKeys, onlyTruthy, prefix, runAt, wait, xlate } from './misc' import { PORT_DISABLED, ADMIN_URI, argv, DEV, IS_WINDOWS } from './const' import findProcess from 'find-process' import { anyAccountCanLoginAdmin } from './adminApis' @@ -168,6 +168,14 @@ export const httpsPortCfg = defineConfig('https_port', PORT_DISABLED) httpsPortCfg.sub(considerHttps) listenInterface.sub(considerHttps) +function renderHost(host: string) { + return xlate(host, { + '0.0.0.0': "any IPv4", + '::': "any IPv6", + '': "any network", + }) +} + interface StartServer { port: number, host?:string } export function startServer(srv: typeof httpSrv, { port, host }: StartServer) { return new Promise(async resolve => { @@ -179,7 +187,7 @@ export function startServer(srv: typeof httpSrv, { port, host }: StartServer) { srv.on('checkContinue', (req, res) => srv.emit('request', req, res)) port = await listen(host) if (port) - console.log(srv.name, "serving on", host||"any network", ':', port) + console.log(srv.name, "serving on", renderHost(host || ''), ':', port) resolve(port) } catch(e) { @@ -272,8 +280,9 @@ const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|a const isLinkLocal = makeNetMatcher('169.254.0.0/16|FE80::/16') export async function getIps(external=true) { + const only = { '0.0.0.0': 'IPv4', '::' : 'IPv6' }[listenInterface.get()] || '' const ips = onlyTruthy(Object.entries(networkInterfaces()).flatMap(([name, nets]) => - nets && !ignore.test(name) && nets.map(net => !net.internal && net.address) + nets && !ignore.test(name) && nets.map(net => !net.internal && (!only || only === net.family) && net.address) )) const e = external && defaultBaseUrl.externalIp if (e && !ips.includes(e)) @@ -289,7 +298,7 @@ export async function getIps(external=true) { export async function getUrls() { const on = listenInterface.get() - const ips = on ? [on] : await getIps() + const ips = on === renderHost(on) ? [on] : await getIps() return Object.fromEntries(onlyTruthy([httpSrv, httpsSrv].map(srv => { if (!srv?.listening) return false