diff --git a/packages/server/src/server/__tests__/network-addresses.test.ts b/packages/server/src/server/__tests__/network-addresses.test.ts index a4955937..a6d47767 100644 --- a/packages/server/src/server/__tests__/network-addresses.test.ts +++ b/packages/server/src/server/__tests__/network-addresses.test.ts @@ -43,6 +43,38 @@ describe("resolveRemoteAddresses", () => { assert.equal(result.primaryRemoteUrl, "https://192.168.1.128:9898") }) }) + + it("prefers private LAN addresses over public addresses", () => { + const addresses = [ + { address: "203.0.113.40", family: "IPv4", internal: false }, + { address: "192.168.1.128", family: "IPv4", internal: false }, + { address: "8.8.8.8", family: "IPv4", internal: false }, + ] + + usingMockedNetworkInterfaces(addresses, () => { + const result = resolveRemoteAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 }) + + assert.deepEqual( + result.userVisible.map((entry) => entry.ip), + ["192.168.1.128", "203.0.113.40", "8.8.8.8"], + ) + assert.equal(result.primaryRemoteUrl, "https://192.168.1.128:9898") + }) + }) + + it("uses a public address when no private LAN address is available", () => { + const addresses = [ + { address: "169.254.10.20", family: "IPv4", internal: false }, + { address: "203.0.113.40", family: "IPv4", internal: false }, + ] + + usingMockedNetworkInterfaces(addresses, () => { + const result = resolveRemoteAddresses({ host: "0.0.0.0", protocol: "https", port: 9898 }) + + assert.deepEqual(result.userVisible.map((entry) => entry.ip), ["203.0.113.40", "169.254.10.20"]) + assert.equal(result.primaryRemoteUrl, "https://203.0.113.40:9898") + }) + }) }) function usingMockedNetworkInterfaces( diff --git a/packages/server/src/server/network-addresses.ts b/packages/server/src/server/network-addresses.ts index 09255835..8491fc82 100644 --- a/packages/server/src/server/network-addresses.ts +++ b/packages/server/src/server/network-addresses.ts @@ -88,7 +88,9 @@ function sortUserVisibleAddresses(addresses: NetworkAddress[]): NetworkAddress[] } function getUserVisiblePriority(ip: string): number { - return isLinkLocalIPv4(ip) ? 1 : 0 + if (isPrivateIPv4(ip)) return 0 + if (isLinkLocalIPv4(ip)) return 2 + return 1 } function isLinkLocalIPv4(ip: string): boolean { @@ -98,6 +100,16 @@ function isLinkLocalIPv4(ip: string): boolean { return first === 169 && second === 254 } +function isPrivateIPv4(ip: string): boolean { + const octets = parseIPv4(ip) + if (!octets) return false + const [first, second] = octets + + if (first === 10) return true + if (first === 192 && second === 168) return true + return first === 172 && second >= 16 && second <= 31 +} + function parseIPv4(value: string): number[] | null { if (!isIPv4Address(value)) return null return value.split(".").map((part) => Number(part))